Compare commits

..
Author SHA1 Message Date
trurl bd61e58821 Don't pass wmalloc'd memory to XFree.
This is enough to get the wmaker into a minimally running state. We should
complete this change by reviewing vitrine/wmaker#1.
2025-10-23 14:46:56 -04:00
trurl 1a8d99b2c0 PropList array items should return NULL on OOB index access. 2025-10-23 14:46:56 -04:00
trurl 7bded0055f Drop unused wAbort function from WPrefs.app. 2025-10-23 14:46:56 -04:00
trurl 32c40643c2 Replace WUtil hashtable with a Rust impl.
This tweaks the hashtable API, and it is incomplete because the WUtil proplist
impl depends heavily on a feature of the old API that is being discontinued.

Moving the proplist code into Rust is our next objective.
2025-10-23 14:46:56 -04:00
trurl 157a8e0d5a Eliminate the retainKey and releaseKey hashtable callbacks.
These fields are only ever NULL, so there's no reason to keep them.
2025-10-23 14:46:56 -04:00
trurl 252362545e Drop WMStringHashCallbacks, which is unused. 2025-10-23 14:46:56 -04:00
trurl f69227ce19 Remove support for PropList data nodes.
This type of PropList entry appears to be completely unused, so let's be rid of
it. This can be reversed in the future if we do want more complete support for
property lists, but for now it's code that we don't need.
2025-10-23 14:46:56 -04:00
trurl bc163d13f6 Provide alloc_string impl. 2025-10-23 14:46:56 -04:00
trurl 2d2dd9febe Chasing memory bugs: be consistent about wmalloc/wfree. 2025-10-23 14:46:56 -04:00
trurl 14c316615e Chasing memory bugs in memory.rs: copy only the payload length in wrealloc. 2025-10-23 14:46:56 -04:00
trurl 6a98614d13 Clarify wmalloc contract - it's safe to read before writing. 2025-10-23 14:46:56 -04:00
trurl dfdaa67b4d Chasing memory bugs in memory.rs: allocate the right layout. 2025-10-23 14:46:56 -04:00
trurl e6fd7e49f8 Clean things up a little bit with refutable let. 2025-10-23 14:46:56 -04:00
trurl 12930739ec Allocate PropList description with memory::alloc_bytes.
All memory allocations passed back from FFI functions should be allocated with
`memory::alloc_bytes`, so that C code can call `memory::free_bytes` when it's
done with them.
2025-10-23 14:46:56 -04:00
trurl cf588d6e27 Simplify path_from_cstr substantially (h/t cross). 2025-10-23 14:46:56 -04:00
trurl f3961ba66f Reimplement the PropList data structure in Rust.
While this large change has some unit tests, it has not been integration tested
thoroughly. Removing the global case insensitivity flag may be an issue in
particular.

A few of PropList API functions have been modified (mostly to get rid of
varargs). The definition of one such function has been left in C for cleanup
later.
2025-10-23 14:46:56 -04:00
trurl 5b593fb19a Expose the GSUSER_SUBDIR preprocessor symbol to Rust.
This symbol's value must be known to port `wmkdirhier` and `wrmdirhier` from
`proplist.c` to Rust.

This change introduces a basic C library under wutil-rs that is linked into the
Rust code to expose preprocessor symbols and other Autotools configuration
decisions to Rust. See the rust rewrite notes at the top of
`wutil-rs/src/defines.rs` for further thoughts.
2025-10-23 14:46:56 -04:00
trurl f2e9123db6 Port WINGs data.c (WMData) to Rust.
This is not tested super well, but I hope we can be rid of it soon enough. (Once
we have WMPropList migrated, WMData and other WINGs data structures should be
easier to prune.)
2025-10-23 14:46:56 -04:00
trurl 791149fe70 Correct oversight in how WMMapArray is supposed to work. 2025-10-23 14:46:56 -04:00
trurl 52b0e6b182 Drop WMData's destructor field.
WMData always owns its data and allocates it with wmalloc, so we can always free
it with wfree (and don't need to call anything else).
2025-10-23 14:46:56 -04:00
trurl bbcf40ee47 Eliminate the WINGs function WMCreateDataWithBytesNoCopy.
This constructor was only needed in one particular place. We can duplidate the
data instead of borrowing it. This ensures that WMData always owns its data
segment, which simplifies porting to Rust significantly.
2025-10-23 14:46:56 -04:00
trurl 9d07e2d3d8 Eliminate the unused WINGs function WMGetSubdataWithRange. 2025-10-23 14:46:56 -04:00
trurl dea8c36cd5 Eliminate the unused WINGs function WMCreatePLDataWithBytesNoCopy. 2025-10-23 14:46:56 -04:00
trurl 3beadbb6cb Forgot to add new array.rs impl. 2025-10-23 14:46:56 -04:00
trurl dd36130730 Fix const qualifier on strings returned by wgethomedir. 2025-10-23 14:46:56 -04:00
trurl 6ede7a5cb0 Reimplement WINGs array.c in Rust.
This is another utility that should not be used in any new (Rust) code. (We
should prefer Vec or something similar.) This should be removed once dependents
are ported to Rust.
2025-10-23 14:46:56 -04:00
trurl 2b9b915768 Replace most WUtil functions in findfile.c with Rust impls.
This is not a bug-for-bug reimplementation, and it may need some shaking down to
ensure that everything still works. Once their dependents are ported, it would
be appropriate to dispose of them.
2025-10-23 14:46:56 -04:00
trurl d50adaa1c8 Use free() on memory returned by FcNameUnparse and hand back wfree-managed pointers from our functions.
This is necessary because we now allocate memory through a special allocator of
our own on the Rust side. Passing raw malloc'd pointers to wfree will break
things.
2025-10-23 14:46:56 -04:00
trurl 5b0ad78f01 Remember to AC_SUBST the Rust compiler environment variables so they're visible in Makefiles. 2025-10-23 14:46:56 -04:00
trurl 46fcbb0ff1 Port custom allocators (WINGs memory.c) to Rust.
This introduces the crate wutil-rs, which is intended to be the destination for
migrating the API of WINGs/WINGs/WUtil.h to Rust.
2025-10-23 14:46:56 -04:00
trurl e3fb8ddbc8 Rip out Boehm GC support.
This is done to simplify memory management across the boundary between C and
Rust. While rewriting WINGs, we may want to be able to malloc/free with the libc
allocator on both sides of that divide.
2025-10-23 14:46:56 -04:00
trurl 59ad67f4dc Provide a janky script for running Window Maker in a captive Xephyr X server.
If this becomes a permanent fixture, hard-coded values should be fixed.
2025-10-23 14:39:53 -04:00
52 changed files with 3840 additions and 2478 deletions
+1 -1
View File
@@ -20,6 +20,7 @@ libWUtil_la_LIBADD = @LIBBSD@ $(wutilrs)
EXTRA_DIST = BUGS make-rgb Examples Extras Tests
# wbutton.c
libWINGs_la_SOURCES = \
configuration.c \
@@ -69,7 +70,6 @@ libWUtil_la_SOURCES = \
error.h \
findfile.c \
handlers.c \
hashtable.c \
menuparser.c \
menuparser.h \
menuparser_macros.c \
+18 -35
View File
@@ -169,10 +169,6 @@ typedef struct {
unsigned (*hash)(const void *);
/* NULL is pointer compare */
Bool (*keyIsEqual)(const void *, const void *);
/* NULL does nothing */
void* (*retainKey)(const void *);
/* NULL does nothing */
void (*releaseKey)(const void *);
} WMHashTableCallbacks;
@@ -249,6 +245,9 @@ char* wgethomedir(void);
/* ---[ WINGs/proplist.c ]------------------------------------------------ */
/*
* Creates the directory path and all its parents.
*/
int wmkdirhier(const char *path);
int wrmdirhier(const char *path);
@@ -338,7 +337,8 @@ void WHandleEvents(void);
/* ---[ WINGs/hashtable.c ]----------------------------------------------- */
WMHashTable* WMCreateHashTable(const WMHashTableCallbacks callbacks);
WMHashTable* WMCreateIdentityHashTable();
WMHashTable* WMCreateStringHashTable();
void WMFreeHashTable(WMHashTable *table);
@@ -388,10 +388,6 @@ Bool WMNextHashEnumeratorItemAndKey(WMHashEnumerator *enumerator,
extern const WMHashTableCallbacks WMIntHashCallbacks;
/* sizeof(keys) are <= sizeof(void*) */
extern const WMHashTableCallbacks WMStringHashCallbacks;
/* keys are strings. Strings will be copied with wstrdup()
* and freed with wfree() */
extern const WMHashTableCallbacks WMStringPointerHashCallbacks;
/* keys are strings, but they are not copied */
@@ -719,21 +715,23 @@ void WMEnqueueCoalesceNotification(WMNotificationQueue *queue,
unsigned coalesceMask);
/* ---[ WINGs/proplist.c ]------------------------------------------------ */
/* Property Lists handling */
void WMPLSetCaseSensitive(Bool caseSensitive);
WMPropList* WMCreatePLString(const char *str);
WMPropList* WMCreatePLData(WMData *data);
WMPropList* WMCreatePLDataWithBytes(const unsigned char *bytes, unsigned int length);
/* ---[ WINGs/proplist.c ]------------------------------------------------ */
WMPropList* WMCreatePLArray(WMPropList *elem, ...);
WMPropList* WMCreatePLDictionary(WMPropList *key, WMPropList *value, ...);
/* ---[ wutil-rs/src/prop_list.rs ]--------------------------------------- */
WMPropList* WMCreatePLString(const char *str);
WMPropList* WMCreatePLArrayFromSlice(WMPropList *elems, unsigned int length);
WMPropList* WMCreateEmptyPLArray();
WMPropList* WMCreatePLDictionary(WMPropList *key, WMPropList *value);
WMPropList* WMCreateEmptyPLDictionary();
WMPropList* WMRetainPropList(WMPropList *plist);
@@ -771,8 +769,6 @@ int WMGetPropListItemCount(WMPropList *plist);
Bool WMIsPLString(WMPropList *plist);
Bool WMIsPLData(WMPropList *plist);
Bool WMIsPLArray(WMPropList *plist);
Bool WMIsPLDictionary(WMPropList *plist);
@@ -782,14 +778,6 @@ Bool WMIsPropListEqualTo(WMPropList *plist, WMPropList *other);
/* Returns a reference. Do not free it! */
char* WMGetFromPLString(WMPropList *plist);
/* Returns a reference. Do not free it! */
WMData* WMGetFromPLData(WMPropList *plist);
/* Returns a reference. Do not free it! */
const unsigned char* WMGetPLDataBytes(WMPropList *plist);
int WMGetPLDataLength(WMPropList *plist);
/* Returns a reference. */
WMPropList* WMGetFromPLArray(WMPropList *plist, int index);
@@ -797,14 +785,9 @@ WMPropList* WMGetFromPLArray(WMPropList *plist, int index);
WMPropList* WMGetFromPLDictionary(WMPropList *plist, WMPropList *key);
/* Returns a PropList array with all the dictionary keys. Release it when
* you're done. Keys in array are retained from the original dictionary
* not copied and need NOT to be released individually. */
* you're done. */
WMPropList* WMGetPLDictionaryKeys(WMPropList *plist);
/* Creates only the first level deep object. All the elements inside are
* retained from the original */
WMPropList* WMShallowCopyPropList(WMPropList *plist);
/* Makes a completely separate replica of the original proplist */
WMPropList* WMDeepCopyPropList(WMPropList *plist);
-422
View File
@@ -1,422 +0,0 @@
#include <config.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "WUtil.h"
#define INITIAL_CAPACITY 23
typedef struct HashItem {
const void *key;
const void *data;
struct HashItem *next; /* collided item list */
} HashItem;
typedef struct W_HashTable {
WMHashTableCallbacks callbacks;
unsigned itemCount;
unsigned size; /* table size */
HashItem **table;
} HashTable;
#define HASH(table, key) (((table)->callbacks.hash ? \
(*(table)->callbacks.hash)(key) : hashPtr(key)) % (table)->size)
#define DUPKEY(table, key) ((table)->callbacks.retainKey ? \
(*(table)->callbacks.retainKey)(key) : (key))
#define RELKEY(table, key) if ((table)->callbacks.releaseKey) \
(*(table)->callbacks.releaseKey)(key)
static inline unsigned hashString(const void *param)
{
const char *key = param;
unsigned ret = 0;
unsigned ctr = 0;
while (*key) {
ret ^= *key++ << ctr;
ctr = (ctr + 1) % sizeof(char *);
}
return ret;
}
static inline unsigned hashPtr(const void *key)
{
return ((size_t) key / sizeof(char *));
}
static void rellocateItem(WMHashTable * table, HashItem * item)
{
unsigned h;
h = HASH(table, item->key);
item->next = table->table[h];
table->table[h] = item;
}
static void rebuildTable(WMHashTable * table)
{
HashItem *next;
HashItem **oldArray;
int i;
int oldSize;
int newSize;
oldArray = table->table;
oldSize = table->size;
newSize = table->size * 2;
table->table = wmalloc(sizeof(char *) * newSize);
table->size = newSize;
for (i = 0; i < oldSize; i++) {
while (oldArray[i] != NULL) {
next = oldArray[i]->next;
rellocateItem(table, oldArray[i]);
oldArray[i] = next;
}
}
wfree(oldArray);
}
WMHashTable *WMCreateHashTable(const WMHashTableCallbacks callbacks)
{
HashTable *table;
table = wmalloc(sizeof(HashTable));
table->callbacks = callbacks;
table->size = INITIAL_CAPACITY;
table->table = wmalloc(sizeof(HashItem *) * table->size);
return table;
}
void WMResetHashTable(WMHashTable * table)
{
HashItem *item, *tmp;
int i;
for (i = 0; i < table->size; i++) {
item = table->table[i];
while (item) {
tmp = item->next;
RELKEY(table, item->key);
wfree(item);
item = tmp;
}
}
table->itemCount = 0;
if (table->size > INITIAL_CAPACITY) {
wfree(table->table);
table->size = INITIAL_CAPACITY;
table->table = wmalloc(sizeof(HashItem *) * table->size);
} else {
memset(table->table, 0, sizeof(HashItem *) * table->size);
}
}
void WMFreeHashTable(WMHashTable * table)
{
HashItem *item, *tmp;
int i;
for (i = 0; i < table->size; i++) {
item = table->table[i];
while (item) {
tmp = item->next;
RELKEY(table, item->key);
wfree(item);
item = tmp;
}
}
wfree(table->table);
wfree(table);
}
unsigned WMCountHashTable(WMHashTable * table)
{
return table->itemCount;
}
static HashItem *hashGetItem(WMHashTable *table, const void *key)
{
unsigned h;
HashItem *item;
h = HASH(table, key);
item = table->table[h];
if (table->callbacks.keyIsEqual) {
while (item) {
if ((*table->callbacks.keyIsEqual) (key, item->key)) {
break;
}
item = item->next;
}
} else {
while (item) {
if (key == item->key) {
break;
}
item = item->next;
}
}
return item;
}
void *WMHashGet(WMHashTable * table, const void *key)
{
HashItem *item;
item = hashGetItem(table, key);
if (!item)
return NULL;
return (void *)item->data;
}
Bool WMHashGetItemAndKey(WMHashTable * table, const void *key, void **retItem, void **retKey)
{
HashItem *item;
item = hashGetItem(table, key);
if (!item)
return False;
if (retKey)
*retKey = (void *)item->key;
if (retItem)
*retItem = (void *)item->data;
return True;
}
void *WMHashInsert(WMHashTable * table, const void *key, const void *data)
{
unsigned h;
HashItem *item;
int replacing = 0;
h = HASH(table, key);
/* look for the entry */
item = table->table[h];
if (table->callbacks.keyIsEqual) {
while (item) {
if ((*table->callbacks.keyIsEqual) (key, item->key)) {
replacing = 1;
break;
}
item = item->next;
}
} else {
while (item) {
if (key == item->key) {
replacing = 1;
break;
}
item = item->next;
}
}
if (replacing) {
const void *old;
old = item->data;
item->data = data;
RELKEY(table, item->key);
item->key = DUPKEY(table, key);
return (void *)old;
} else {
HashItem *nitem;
nitem = wmalloc(sizeof(HashItem));
nitem->key = DUPKEY(table, key);
nitem->data = data;
nitem->next = table->table[h];
table->table[h] = nitem;
table->itemCount++;
}
/* OPTIMIZE: put this in an idle handler. */
if (table->itemCount > table->size) {
#ifdef DEBUG0
printf("rebuilding hash table...\n");
#endif
rebuildTable(table);
#ifdef DEBUG0
printf("finished rebuild.\n");
#endif
}
return NULL;
}
static HashItem *deleteFromList(HashTable * table, HashItem * item, const void *key)
{
HashItem *next;
if (item == NULL)
return NULL;
if ((table->callbacks.keyIsEqual && (*table->callbacks.keyIsEqual) (key, item->key))
|| (!table->callbacks.keyIsEqual && key == item->key)) {
next = item->next;
RELKEY(table, item->key);
wfree(item);
table->itemCount--;
return next;
}
item->next = deleteFromList(table, item->next, key);
return item;
}
void WMHashRemove(WMHashTable * table, const void *key)
{
unsigned h;
h = HASH(table, key);
table->table[h] = deleteFromList(table, table->table[h], key);
}
WMHashEnumerator WMEnumerateHashTable(WMHashTable * table)
{
WMHashEnumerator enumerator;
enumerator.table = table;
enumerator.index = 0;
enumerator.nextItem = table->table[0];
return enumerator;
}
void *WMNextHashEnumeratorItem(WMHashEnumerator * enumerator)
{
const void *data = NULL;
/* this assumes the table doesn't change between
* WMEnumerateHashTable() and WMNextHashEnumeratorItem() calls */
if (enumerator->nextItem == NULL) {
HashTable *table = enumerator->table;
while (++enumerator->index < table->size) {
if (table->table[enumerator->index] != NULL) {
enumerator->nextItem = table->table[enumerator->index];
break;
}
}
}
if (enumerator->nextItem) {
data = ((HashItem *) enumerator->nextItem)->data;
enumerator->nextItem = ((HashItem *) enumerator->nextItem)->next;
}
return (void *)data;
}
void *WMNextHashEnumeratorKey(WMHashEnumerator * enumerator)
{
const void *key = NULL;
/* this assumes the table doesn't change between
* WMEnumerateHashTable() and WMNextHashEnumeratorKey() calls */
if (enumerator->nextItem == NULL) {
HashTable *table = enumerator->table;
while (++enumerator->index < table->size) {
if (table->table[enumerator->index] != NULL) {
enumerator->nextItem = table->table[enumerator->index];
break;
}
}
}
if (enumerator->nextItem) {
key = ((HashItem *) enumerator->nextItem)->key;
enumerator->nextItem = ((HashItem *) enumerator->nextItem)->next;
}
return (void *)key;
}
Bool WMNextHashEnumeratorItemAndKey(WMHashEnumerator * enumerator, void **item, void **key)
{
/* this assumes the table doesn't change between
* WMEnumerateHashTable() and WMNextHashEnumeratorItemAndKey() calls */
if (enumerator->nextItem == NULL) {
HashTable *table = enumerator->table;
while (++enumerator->index < table->size) {
if (table->table[enumerator->index] != NULL) {
enumerator->nextItem = table->table[enumerator->index];
break;
}
}
}
if (enumerator->nextItem) {
if (item)
*item = (void *)((HashItem *) enumerator->nextItem)->data;
if (key)
*key = (void *)((HashItem *) enumerator->nextItem)->key;
enumerator->nextItem = ((HashItem *) enumerator->nextItem)->next;
return True;
}
return False;
}
static Bool compareStrings(const void *param1, const void *param2)
{
const char *key1 = param1;
const char *key2 = param2;
return strcmp(key1, key2) == 0;
}
typedef void *(*retainFunc) (const void *);
typedef void (*releaseFunc) (const void *);
const WMHashTableCallbacks WMIntHashCallbacks = {
NULL,
NULL,
NULL,
NULL
};
const WMHashTableCallbacks WMStringHashCallbacks = {
hashString,
compareStrings,
(retainFunc) wstrdup,
(releaseFunc) wfree
};
const WMHashTableCallbacks WMStringPointerHashCallbacks = {
hashString,
compareStrings,
NULL,
NULL
};
+3 -3
View File
@@ -88,10 +88,10 @@ static NotificationCenter *notificationCenter = NULL;
void W_InitNotificationCenter(void)
{
notificationCenter = wmalloc(sizeof(NotificationCenter));
notificationCenter->nameTable = WMCreateHashTable(WMStringPointerHashCallbacks);
notificationCenter->objectTable = WMCreateHashTable(WMIntHashCallbacks);
notificationCenter->nameTable = WMCreateStringHashTable();
notificationCenter->objectTable = WMCreateIdentityHashTable();
notificationCenter->nilList = NULL;
notificationCenter->observerTable = WMCreateHashTable(WMIntHashCallbacks);
notificationCenter->observerTable = WMCreateIdentityHashTable();
}
void W_ReleaseNotificationCenter(void)
-1852
View File
File diff suppressed because it is too large Load Diff
+2 -35
View File
@@ -46,39 +46,6 @@ static void synchronizeUserDefaults(void *foo);
#define UD_SYNC_INTERVAL 2000
#endif
const char *wusergnusteppath(void)
{
static const char subdir[] = "/" GSUSER_SUBDIR;
static char *path = NULL;
char *gspath;
char *h;
int pathlen;
if (path)
/* Value have been already computed, re-use it */
return path;
gspath = GETENV("WMAKER_USER_ROOT");
if (gspath) {
gspath = wexpandpath(gspath);
if (gspath) {
path = gspath;
return path;
}
wwarning(_("variable WMAKER_USER_ROOT defined with invalid path, not used"));
}
h = wgethomedir();
pathlen = strlen(h);
path = wmalloc(pathlen + sizeof(subdir));
strcpy(path, h);
strcpy(path + pathlen, subdir);
wfree(h);
return path;
}
const char *wuserdatapath(void)
{
static char *path = NULL;
@@ -330,7 +297,7 @@ WMUserDefaults *WMGetStandardUserDefaults(void)
/* terminate list */
defaults->searchList[2] = NULL;
defaults->searchListArray = WMCreatePLArray(NULL, NULL);
defaults->searchListArray = WMCreateEmptyPLArray();
i = 0;
while (defaults->searchList[i]) {
@@ -403,7 +370,7 @@ WMUserDefaults *WMGetDefaultsFromPath(const char *path)
/* terminate list */
defaults->searchList[1] = NULL;
defaults->searchListArray = WMCreatePLArray(NULL, NULL);
defaults->searchListArray = WMCreateEmptyPLArray();
i = 0;
while (defaults->searchList[i]) {
+1 -1
View File
@@ -65,7 +65,7 @@ struct W_Balloon *W_CreateBalloon(WMScreen * scr)
W_ResizeView(bPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
bPtr->flags.alignment = DEFAULT_ALIGNMENT;
bPtr->table = WMCreateHashTable(WMIntHashCallbacks);
bPtr->table = WMCreateIdentityHashTable();
bPtr->delay = DEFAULT_DELAY;
+1 -1
View File
@@ -535,7 +535,7 @@ static void listFamilies(WMScreen * scr, WMFontPanel * panel)
if (pat)
FcPatternDestroy(pat);
families = WMCreateHashTable(WMStringPointerHashCallbacks);
families = WMCreateStringHashTable();
if (fs) {
for (i = 0; i < fs->nfont; i++) {
+1 -1
View File
@@ -630,7 +630,7 @@ WMScreen *WMCreateScreenWithRContext(Display * display, int screen, RContext * c
scrPtr->rootWin = RootWindow(display, screen);
scrPtr->fontCache = WMCreateHashTable(WMStringPointerHashCallbacks);
scrPtr->fontCache = WMCreateStringHashTable();
scrPtr->xftdraw = XftDrawCreate(scrPtr->display, W_DRAWABLE(scrPtr), scrPtr->visual, scrPtr->colormap);
+2 -2
View File
@@ -2228,7 +2228,7 @@ static void prepareForClose(_Panel * panel)
WMUserDefaults *udb = WMGetStandardUserDefaults();
int i;
textureList = WMCreatePLArray(NULL, NULL);
textureList = WMCreateEmptyPLArray();
/* store list of textures */
for (i = 8; i < WMGetListNumberOfRows(panel->texLs); i++) {
@@ -2250,7 +2250,7 @@ static void prepareForClose(_Panel * panel)
WMReleasePropList(textureList);
/* store list of colors */
textureList = WMCreatePLArray(NULL, NULL);
textureList = WMCreateEmptyPLArray();
for (i = 0; i < wlengthof(sample_colors); i++) {
WMColor *color;
char *str;
+1 -1
View File
@@ -151,7 +151,7 @@ static void storeData(_Panel * panel)
SetIntegerForKey(WMGetSliderValue(panel->hceS), "HotCornerEdge");
list = WMCreatePLArray(NULL, NULL);
list = WMCreateEmptyPLArray();
for (i = 0; i < sizeof(panel->hcactionsT) / sizeof(WMTextField *); i++) {
str = WMGetTextFieldText(panel->hcactionsT[i]);
if (strlen(str) == 0)
+1
View File
@@ -67,6 +67,7 @@ WPrefs_DEPENDENCIES = $(top_builddir)/WINGs/libWINGs.la
WPrefs_LDADD = \
$(top_builddir)/wutil-rs/target/debug/libwutil_rs.a\
$(top_builddir)/wings-rs/target/debug/libwings_rs.la\
$(top_builddir)/WINGs/libWINGs.la\
$(top_builddir)/WINGs/libWUtil.la\
$(top_builddir)/wrlib/libwraster.la \
+2 -2
View File
@@ -203,7 +203,7 @@ static void storeData(_Panel * panel)
int i;
char *p;
list = WMCreatePLArray(NULL, NULL);
list = WMCreateEmptyPLArray();
for (i = 0; i < WMGetListNumberOfRows(panel->icoL); i++) {
p = WMGetListItem(panel->icoL, i)->text;
tmp = WMCreatePLString(p);
@@ -211,7 +211,7 @@ static void storeData(_Panel * panel)
}
SetObjectForKey(list, "IconPath");
list = WMCreatePLArray(NULL, NULL);
list = WMCreateEmptyPLArray();
for (i = 0; i < WMGetListNumberOfRows(panel->pixL); i++) {
p = WMGetListItem(panel->pixL, i)->text;
tmp = WMCreatePLString(p);
+2 -2
View File
@@ -705,10 +705,10 @@ static void loadConfigurations(WMScreen * scr, WMWindow * mainw)
}
if (!db) {
db = WMCreatePLDictionary(NULL, NULL);
db = WMCreateEmptyPLDictionary();
}
if (!gdb) {
gdb = WMCreatePLDictionary(NULL, NULL);
gdb = WMCreateEmptyPLDictionary();
}
GlobalDB = gdb;
+6 -9
View File
@@ -47,14 +47,6 @@ struct {
static pid_t DeadChildren[MAX_DEATHS];
static int DeadChildrenCount = 0;
static noreturn void wAbort(Bool foo)
{
/* Parameter not used, but tell the compiler that it is ok */
(void) foo;
exit(1);
}
static void print_help(const char *progname)
{
printf(_("usage: %s [options]\n"), progname);
@@ -153,7 +145,12 @@ int main(int argc, char **argv)
exit(0);
}
WMPLSetCaseSensitive(False);
/*
* Rust rewrite note: this API surface has been removed, but we leave in
* a record of where it was invoked to set a value other than the
* default, in case it helps to track down bugs in the future.
*/
/* WMPLSetCaseSensitive(False); */
Initialize(scr);
+2 -1
View File
@@ -134,7 +134,7 @@ else
nodist_wmaker_SOURCES = misc.hack_nf.c \
xmodifier.hack_nf.c
CLEANFILES = $(nodist_wmaker_SOURCES) ../wmaker-rs/target
CLEANFILES = $(nodist_wmaker_SOURCES)
misc.hack_nf.c: misc.c $(top_srcdir)/script/nested-func-to-macro.sh
$(AM_V_GEN)$(top_srcdir)/script/nested-func-to-macro.sh \
@@ -160,6 +160,7 @@ wmaker_LDADD = \
$(top_builddir)/WINGs/libWINGs.la\
$(top_builddir)/WINGs/libWUtil.la\
$(top_builddir)/wrlib/libwraster.la\
$(top_builddir)/wutil-rs/target/debug/libwutil_rs.a\
$(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a\
@XLFLAGS@ \
@LIBXRANDR@ \
+1 -1
View File
@@ -1360,7 +1360,7 @@ static void wApplicationSaveIconPathFor(const char *iconPath, const char *wm_ins
val = WMGetFromPLDictionary(adict, iconk);
} else {
/* no dictionary for app, so create one */
adict = WMCreatePLDictionary(NULL, NULL);
adict = WMCreateEmptyPLDictionary();
WMPutInPLDictionary(dict, key, adict);
WMReleasePropList(adict);
val = NULL;
+2 -2
View File
@@ -139,7 +139,7 @@ static WMenu *parseMenuCommand(WScreen * scr, Window win, char **slist, int coun
}
wstrlcpy(title, &slist[*index][pos], sizeof(title));
}
data = malloc(sizeof(WAppMenuData));
data = wmalloc(sizeof(WAppMenuData));
if (data == NULL) {
wwarning(_("appmenu: out of memory creating menu for window %lx"), win);
wMenuDestroy(menu, True);
@@ -152,7 +152,7 @@ static WMenu *parseMenuCommand(WScreen * scr, Window win, char **slist, int coun
if (!entry) {
wMenuDestroy(menu, True);
wwarning(_("appmenu: out of memory creating menu for window %lx"), win);
free(data);
wfree(data);
return NULL;
}
if (rtext[0] != 0)
+2 -2
View File
@@ -307,7 +307,7 @@ void wClientCheckProperty(WWindow * wwin, XPropertyEvent * event)
wWindowUpdateName(wwin, tmp);
}
if (tmp)
XFree(tmp);
wfree(tmp);
}
break;
@@ -616,7 +616,7 @@ void wClientCheckProperty(WWindow * wwin, XPropertyEvent * event)
wWindowUpdateGNUstepAttr(wwin, attr);
XFree(attr);
wfree(attr);
} else {
wNETWMCheckClientHintChange(wwin, event);
}
+6 -1
View File
@@ -862,7 +862,12 @@ static void initDefaults(void)
unsigned int i;
WDefaultEntry *entry;
WMPLSetCaseSensitive(False);
/*
* Rust rewrite note: this API surface has been removed, but we leave in
* a record of where it was invoked to set a value other than the
* default, in case it helps to track down bugs in the future.
*/
/* WMPLSetCaseSensitive(False); */
for (i = 0; i < wlengthof(optionList); i++) {
entry = &optionList[i];
+1 -1
View File
@@ -250,7 +250,7 @@ static void SaveHistory(WMArray * history, const char *filename)
int i;
WMPropList *plhistory;
plhistory = WMCreatePLArray(NULL);
plhistory = WMCreateEmptyPLArray();
for (i = 0; i < WMGetArrayItemCount(history); ++i)
WMAddToPLArray(plhistory, WMCreatePLString(WMGetFromArray(history, i)));
+11 -9
View File
@@ -1603,11 +1603,13 @@ static WMPropList *make_icon_state(WAppIcon *btn)
snprintf(buffer, sizeof(buffer), "%hi,%hi", wAppIconGetXIndex(btn), wAppIconGetYIndex(btn));
position = WMCreatePLString(buffer);
node = WMCreatePLDictionary(dCommand, command,
dName, name,
dAutoLaunch, autolaunch,
dLock, lock,
dForced, forced, dBuggyApplication, buggy, dPosition, position, NULL);
node = WMCreatePLDictionary(dCommand, command);
WMPutInPLDictionary(node, dName, name);
WMPutInPLDictionary(node, dAutoLaunch, autolaunch);
WMPutInPLDictionary(node, dLock, lock);
WMPutInPLDictionary(node, dForced, forced);
WMPutInPLDictionary(node, dBuggyApplication, buggy);
WMPutInPLDictionary(node, dPosition, position);
WMReleasePropList(command);
WMReleasePropList(name);
WMReleasePropList(position);
@@ -1642,7 +1644,7 @@ static WMPropList *dockSaveState(WDock *dock)
WMPropList *value, *key;
char buffer[256];
list = WMCreatePLArray(NULL);
list = WMCreateEmptyPLArray();
for (i = (dock->type == WM_DOCK ? 0 : 1); i < dock->max_icons; i++) {
WAppIcon *btn = dock->icon_array[i];
@@ -1657,7 +1659,7 @@ static WMPropList *dockSaveState(WDock *dock)
}
}
dock_state = WMCreatePLDictionary(dApplications, list, NULL);
dock_state = WMCreatePLDictionary(dApplications, list);
if (dock->type == WM_DOCK) {
snprintf(buffer, sizeof(buffer), "Applications%i", dock->screen_ptr->scr_height);
@@ -5072,7 +5074,7 @@ static WMPropList *drawerSaveState(WDock *drawer)
ai = drawer->icon_array[0];
/* Store its name */
pstr = WMCreatePLString(wAppIconGetWmInstance(ai));
drawer_state = WMCreatePLDictionary(dName, pstr, NULL); /* we need this final NULL */
drawer_state = WMCreatePLDictionary(dName, pstr);
WMReleasePropList(pstr);
/* Store its position */
@@ -5114,7 +5116,7 @@ void wDrawersSaveState(WScreen *scr)
make_keys();
all_drawers = WMCreatePLArray(NULL);
all_drawers = WMCreateEmptyPLArray();
for (i=0, dc = scr->drawers;
i < scr->drawer_count;
i++, dc = dc->next) {
+3 -2
View File
@@ -2309,7 +2309,8 @@ static void saveMenuInfo(WMPropList * dict, WMenu * menu, WMPropList * key)
snprintf(buffer, sizeof(buffer), "%i,%i", menu->frame_x, menu->frame_y);
value = WMCreatePLString(buffer);
list = WMCreatePLArray(value, NULL);
list = WMCreateEmptyPLArray();
WMAddToPLArray(list, value);
if (menu->flags.lowered)
WMAddToPLArray(list, WMCreatePLString("lowered"));
WMPutInPLDictionary(dict, key, list);
@@ -2322,7 +2323,7 @@ void wMenuSaveState(WScreen * scr)
WMPropList *menus, *key;
int save_menus = 0;
menus = WMCreatePLDictionary(NULL, NULL);
menus = WMCreateEmptyPLDictionary();
if (scr->switch_menu && scr->switch_menu->flags.buttoned) {
key = WMCreatePLString("SwitchMenu");
+6 -6
View File
@@ -520,7 +520,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline)
len = strlen(cmdline);
olen = len + 1;
out = malloc(olen);
out = wmalloc(olen);
if (!out) {
wwarning(_("out of memory during expansion of \"%s\""), cmdline);
return NULL;
@@ -573,7 +573,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline)
(unsigned int)scr->focused_window->client_win);
slen = strlen(tmpbuf);
olen += slen;
nout = realloc(out, olen);
nout = wrealloc(out, olen);
if (!nout) {
wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%w", cmdline);
goto error;
@@ -590,7 +590,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline)
snprintf(tmpbuf, sizeof(tmpbuf), "0x%x", (unsigned int)scr->current_workspace + 1);
slen = strlen(tmpbuf);
olen += slen;
nout = realloc(out, olen);
nout = wrealloc(out, olen);
if (!nout) {
wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%W", cmdline);
goto error;
@@ -607,7 +607,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline)
if (user_input) {
slen = strlen(user_input);
olen += slen;
nout = realloc(out, olen);
nout = wrealloc(out, olen);
if (!nout) {
wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%a", cmdline);
goto error;
@@ -630,7 +630,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline)
}
slen = strlen(scr->xdestring);
olen += slen;
nout = realloc(out, olen);
nout = wrealloc(out, olen);
if (!nout) {
wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%d", cmdline);
goto error;
@@ -651,7 +651,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline)
}
slen = strlen(selection);
olen += slen;
nout = realloc(out, olen);
nout = wrealloc(out, olen);
if (!nout) {
wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%s", cmdline);
goto error;
+2 -2
View File
@@ -133,7 +133,7 @@ int PropGetGNUstepWMAttr(Window window, GNUstepWMAttributes ** attr)
if (!data)
return False;
*attr = malloc(sizeof(GNUstepWMAttributes));
*attr = wmalloc(sizeof(GNUstepWMAttributes));
if (!*attr) {
XFree(data);
return False;
@@ -183,7 +183,7 @@ void PropSetIconTileHint(WScreen * scr, RImage * image)
imageAtom = XInternAtom(dpy, "_RGBA_IMAGE", False);
}
tmp = malloc(image->width * image->height * 4 + 4);
tmp = wmalloc(image->width * image->height * 4 + 4);
if (!tmp) {
wwarning("could not allocate memory to set _WINDOWMAKER_ICON_TILE hint");
return;
+6 -3
View File
@@ -968,8 +968,6 @@ void wScreenSaveState(WScreen * scr)
old_state = scr->session_state;
scr->session_state = WMCreatePLDictionary(NULL, NULL);
WMPLSetCaseSensitive(True);
/* save dock state to file */
if (!wPreferences.flags.nodock) {
wDockSaveState(scr, old_state);
@@ -1009,8 +1007,13 @@ void wScreenSaveState(WScreen * scr)
WMPutInPLDictionary(scr->session_state, dWorkspace, foo);
}
/*
* Rust rewrite note: this API surface has been removed, but we leave in
* a record of where it was invoked to set a value other than the
* default, in case it helps to track down bugs in the future.
*/
/* clean up */
WMPLSetCaseSensitive(False);
/* WMPLSetCaseSensitive(False); */
wMenuSaveState(scr);
+22 -15
View File
@@ -241,14 +241,15 @@ static WMPropList *makeWindowState(WWindow * wwin, WApplication * wapp)
snprintf(buffer, sizeof(buffer), "%u", mask);
shortcut = WMCreatePLString(buffer);
win_state = WMCreatePLDictionary(sName, name,
sCommand, cmd,
sWorkspace, workspace,
sShaded, shaded,
sMiniaturized, miniaturized,
sMaximized, maximized,
sHidden, hidden,
sShortcutMask, shortcut, sGeometry, geometry, NULL);
win_state = WMCreatePLDictionary(sName, name);
WMPutInPLDictionary(win_state, sCommand, cmd);
WMPutInPLDictionary(win_state, sWorkspace, workspace);
WMPutInPLDictionary(win_state, sShaded, shaded);
WMPutInPLDictionary(win_state, sMiniaturized, miniaturized);
WMPutInPLDictionary(win_state, sMaximized, maximized);
WMPutInPLDictionary(win_state, sHidden, hidden);
WMPutInPLDictionary(win_state, sShortcutMask, shortcut);
WMPutInPLDictionary(win_state, sGeometry, geometry);
WMReleasePropList(name);
WMReleasePropList(cmd);
@@ -313,7 +314,7 @@ void wSessionSaveState(WScreen * scr)
return;
}
list = WMCreatePLArray(NULL);
list = WMCreateEmptyPLArray();
wapp_list = WMCreateArray(16);
@@ -487,8 +488,6 @@ void wSessionRestoreState(WScreen *scr)
if (!scr->session_state)
return;
WMPLSetCaseSensitive(True);
apps = WMGetFromPLDictionary(scr->session_state, sApplications);
if (!apps)
return;
@@ -579,8 +578,13 @@ void wSessionRestoreState(WScreen *scr)
if (class)
wfree(class);
}
/*
* Rust rewrite note: this API surface has been removed, but we leave in
* a record of where it was invoked to set a value other than the
* default, in case it helps to track down bugs in the future.
*/
/* clean up */
WMPLSetCaseSensitive(False);
/* WMPLSetCaseSensitive(False); */
}
void wSessionRestoreLastWorkspace(WScreen * scr)
@@ -594,8 +598,6 @@ void wSessionRestoreLastWorkspace(WScreen * scr)
if (!scr->session_state)
return;
WMPLSetCaseSensitive(True);
wks = WMGetFromPLDictionary(scr->session_state, sWorkspace);
if (!wks || !WMIsPLString(wks))
return;
@@ -605,8 +607,13 @@ void wSessionRestoreLastWorkspace(WScreen * scr)
if (!value)
return;
/*
* Rust rewrite note: this API surface has been removed, but we leave in
* a record of where it was invoked to set a value other than the
* default, in case it helps to track down bugs in the future.
*/
/* clean up */
WMPLSetCaseSensitive(False);
/* WMPLSetCaseSensitive(False); */
/* Get the workspace number for the workspace name */
w = wGetWorkspaceNumber(scr, value);
+32 -17
View File
@@ -174,15 +174,18 @@ static WMPropList *get_value_from_instanceclass(const char *value)
key = WMCreatePLString(value);
WMPLSetCaseSensitive(True);
if (w_global.domain.window_attr->dictionary)
val = key ? WMGetFromPLDictionary(w_global.domain.window_attr->dictionary, key) : NULL;
if (key)
WMReleasePropList(key);
WMPLSetCaseSensitive(False);
/*
* Rust rewrite note: this API surface has been removed, but we leave in
* a record of where it was invoked to set a value other than the
* default, in case it helps to track down bugs in the future.
*/
/* WMPLSetCaseSensitive(False); */
return val;
}
@@ -219,8 +222,6 @@ void wDefaultFillAttributes(const char *instance, const char *class,
dn = get_value_from_instanceclass(instance);
dc = get_value_from_instanceclass(class);
WMPLSetCaseSensitive(True);
if ((w_global.domain.window_attr->dictionary) && (useGlobalDefault))
da = WMGetFromPLDictionary(w_global.domain.window_attr->dictionary, AnyWindow);
@@ -311,8 +312,13 @@ void wDefaultFillAttributes(const char *instance, const char *class,
APPLY_VAL(value, no_language_button, ANoLanguageButton);
#endif
/*
* Rust rewrite note: this API surface has been removed, but we leave in
* a record of where it was invoked to set a value other than the
* default, in case it helps to track down bugs in the future.
*/
/* clean up */
WMPLSetCaseSensitive(False);
/* WMPLSetCaseSensitive(False); */
}
static WMPropList *get_generic_value(const char *instance, const char *class,
@@ -322,8 +328,6 @@ static WMPropList *get_generic_value(const char *instance, const char *class,
value = NULL;
WMPLSetCaseSensitive(True);
/* Search the icon name using class and instance */
if (class && instance) {
char *buffer;
@@ -371,7 +375,12 @@ static WMPropList *get_generic_value(const char *instance, const char *class,
value = WMGetFromPLDictionary(dict, option);
}
WMPLSetCaseSensitive(False);
/*
* Rust rewrite note: this API surface has been removed, but we leave in
* a record of where it was invoked to set a value other than the
* default, in case it helps to track down bugs in the future.
*/
/* WMPLSetCaseSensitive(False); */
return value;
}
@@ -545,15 +554,13 @@ void wDefaultChangeIcon(const char *instance, const char *class, const char *fil
int same = 0;
if (!dict) {
dict = WMCreatePLDictionary(NULL, NULL);
dict = WMCreateEmptyPLDictionary();
if (dict)
db->dictionary = dict;
else
return;
}
WMPLSetCaseSensitive(True);
if (instance && class) {
char *buffer;
@@ -570,7 +577,7 @@ void wDefaultChangeIcon(const char *instance, const char *class, const char *fil
if (file) {
value = WMCreatePLString(file);
icon_value = WMCreatePLDictionary(AIcon, value, NULL);
icon_value = WMCreatePLDictionary(AIcon, value);
WMReleasePropList(value);
def_win = WMGetFromPLDictionary(dict, AnyWindow);
@@ -600,7 +607,12 @@ void wDefaultChangeIcon(const char *instance, const char *class, const char *fil
if (icon_value)
WMReleasePropList(icon_value);
WMPLSetCaseSensitive(False);
/*
* Rust rewrite note: this API surface has been removed, but we leave in
* a record of where it was invoked to set a value other than the
* default, in case it helps to track down bugs in the future.
*/
/* WMPLSetCaseSensitive(False); */
}
void wDefaultPurgeInfo(const char *instance, const char *class)
@@ -612,8 +624,6 @@ void wDefaultPurgeInfo(const char *instance, const char *class)
init_wdefaults();
}
WMPLSetCaseSensitive(True);
buffer = wmalloc(strlen(class) + strlen(instance) + 2);
sprintf(buffer, "%s.%s", instance, class);
key = WMCreatePLString(buffer);
@@ -631,7 +641,12 @@ void wDefaultPurgeInfo(const char *instance, const char *class)
wfree(buffer);
WMReleasePropList(key);
WMPLSetCaseSensitive(False);
/*
* Rust rewrite note: this API surface has been removed, but we leave in
* a record of where it was invoked to set a value other than the
* default, in case it helps to track down bugs in the future.
*/
/* WMPLSetCaseSensitive(False); */
}
/* --------------------------- Local ----------------------- */
+1 -1
View File
@@ -1413,7 +1413,7 @@ WWindow *wManageWindow(WScreen *scr, Window window)
/* Update name must come after WApplication stuff is done */
wWindowUpdateName(wwin, title);
if (title)
XFree(title);
wfree(title);
XUngrabServer(dpy);
+9 -6
View File
@@ -597,7 +597,7 @@ static void saveSettings(WMWidget *button, void *client_data)
dict = db->dictionary;
if (!dict) {
dict = WMCreatePLDictionary(NULL, NULL);
dict = WMCreateEmptyPLDictionary();
if (dict) {
db->dictionary = dict;
} else {
@@ -609,10 +609,8 @@ static void saveSettings(WMWidget *button, void *client_data)
if (showIconFor(WMWidgetScreen(button), panel, NULL, NULL, USE_TEXT_FIELD) < 0)
return;
WMPLSetCaseSensitive(True);
winDic = WMCreatePLDictionary(NULL, NULL);
appDic = WMCreatePLDictionary(NULL, NULL);
winDic = WMCreateEmptyPLDictionary();
appDic = WMCreateEmptyPLDictionary();
/* Save the icon info */
/* The flag "Ignore client suplied icon is not selected" */
@@ -709,8 +707,13 @@ static void saveSettings(WMWidget *button, void *client_data)
UpdateDomainFile(db);
/*
* Rust rewrite note: this API surface has been removed, but we leave in
* a record of where it was invoked to set a value other than the
* default, in case it helps to track down bugs in the future.
*/
/* clean up */
WMPLSetCaseSensitive(False);
/* WMPLSetCaseSensitive(False); */
}
static void applySettings(WMWidget *button, void *client_data)
+2 -2
View File
@@ -852,10 +852,10 @@ void wWorkspaceSaveState(WScreen * scr, WMPropList * old_state)
make_keys();
old_wks_state = WMGetFromPLDictionary(old_state, dWorkspaces);
parr = WMCreatePLArray(NULL);
parr = WMCreateEmptyPLArray();
for (i = 0; i < scr->workspace_count; i++) {
pstr = WMCreatePLString(scr->workspaces[i]->name);
wks_state = WMCreatePLDictionary(dName, pstr, NULL);
wks_state = WMCreatePLDictionary(dName, pstr);
WMReleasePropList(pstr);
if (!wPreferences.flags.noclip) {
pstr = wClipSaveWorkspaceState(scr, i);
+86
View File
@@ -0,0 +1,86 @@
#!/bin/sh
set -e
project_base="$(dirname $0)"
export WMAKER_USER_ROOT="$HOME/tmp/GNUstep"
gs_base="$WMAKER_USER_ROOT"
gs_defaults="$gs_base/Defaults"
gs_system_defaults="$project_base"/WindowMaker/Defaults
wm_base="$gs_base/Library/WindowMaker"
wm_backgrounds="$wm_base/Backgrounds"
wm_iconsets="$wm_base/IconSets"
wm_pixmaps="$wm_base/Pixmaps"
gs_icons="$gs_base/Library/Icons"
wm_style="$wm_base/Style"
wm_styles="$wm_base/Styles"
wm_themes="$wm_base/Themes"
WindowMaker="$project_base/src/.libs/wmaker"
convertfonts="$project_base/util/convertfonts"
make_dir_if_needed ()
{
if [ ! -d "$1" ] ; then
install -m 0755 -d "$1"
fi
}
rename_dir_if_possible ()
{
if [ ! -d "$2" ] ; then
if [ -d "$1" ] ; then
mv "$1" "$2"
fi
fi
}
copy_defaults_if_needed ()
{
file="$gs_defaults/$1"
system_file="$gs_system_defaults/$1"
if [ ! -f "$file" ] ; then
install -m 0644 "$system_file" "$file"
fi
}
make_dir_if_needed "$gs_defaults"
make_dir_if_needed "$wm_base"
make_dir_if_needed "$wm_backgrounds"
make_dir_if_needed "$wm_iconsets"
make_dir_if_needed "$wm_pixmaps"
make_dir_if_needed "$gs_icons"
rename_dir_if_possible "$wm_style" "$wm_styles"
make_dir_if_needed "$wm_styles"
make_dir_if_needed "$wm_themes"
export LD_LIBRARY_PATH="$project_base/wrlib/.libs:$project_base/WINGs/.libs:$LD_LIBRARY_PATH"
copy_defaults_if_needed WindowMaker
copy_defaults_if_needed WMRootMenu
copy_defaults_if_needed WMState
#copy_defaults_if_needed WMWindowAttributes
if [ -x $convertfonts -a ! -e "$wm_base/.fonts_converted" ] ; then
# --keep-xlfd is used in order to preserve the original information
$convertfonts --keep-xlfd "$gs_defaults/WindowMaker"
if [ -f "$gs_defaults/WMGLOBAL" ] ; then
$convertfonts --keep-xlfd "$gs_defaults/WMGLOBAL"
fi
find "$wm_styles" -mindepth 1 -maxdepth 1 -type f -print0 |
xargs -0 -r -n 1 $convertfonts --keep-xlfd
touch "$wm_base/.fonts_converted"
fi
if [ -n "$1" -a -x "$WindowMaker$1" ] ; then
WindowMaker="$WindowMaker$1"
shift
fi
Xephyr -screen 1080x760 :1 &
xephyr_pid=$!
DISPLAY=:1 gdb \
--directory "$project_base" \
--quiet \
--args "$WindowMaker" -display :1 --for-real "$@"
kill $xephyr_pid
+6 -1
View File
@@ -131,7 +131,12 @@ int main(int argc, char **argv)
/* this contradicts big time with getstyle */
setlocale(LC_ALL, "");
WMPLSetCaseSensitive(False);
/*
* Rust rewrite note: this API surface has been removed, but we leave in
* a record of where it was invoked to set a value other than the
* default, in case it helps to track down bugs in the future.
*/
/* WMPLSetCaseSensitive(False); */
style = WMReadPropListFromFile(file);
if (!style) {
+1 -1
View File
@@ -109,7 +109,7 @@ int main(int argc, char **argv)
if (window_attrs && WMIsPLDictionary(window_attrs)) {
icon_value = WMGetFromPLDictionary(window_attrs, icon_key);
if (icon_value) {
icondic = WMCreatePLDictionary(icon_key, icon_value, NULL);
icondic = WMCreatePLDictionary(icon_key, icon_value);
WMPutInPLDictionary(iconset, window_name, icondic);
}
}
+7 -2
View File
@@ -337,7 +337,12 @@ int main(int argc, char **argv)
return 1;
}
WMPLSetCaseSensitive(False);
/*
* Rust rewrite note: this API surface has been removed, but we leave in
* a record of where it was invoked to set a value other than the
* default, in case it helps to track down bugs in the future.
*/
/* WMPLSetCaseSensitive(False); */
path = wdefaultspathfordomain("WindowMaker");
@@ -357,7 +362,7 @@ int main(int argc, char **argv)
prop = val;
}
style = WMCreatePLDictionary(NULL, NULL);
style = WMCreateEmptyPLDictionary();
for (i = 0; options[i] != NULL; i++) {
key = WMCreatePLString(options[i]);
+6 -1
View File
@@ -427,7 +427,12 @@ int main(int argc, char **argv)
file = argv[0];
WMPLSetCaseSensitive(False);
/*
* Rust rewrite note: this API surface has been removed, but we leave in
* a record of where it was invoked to set a value other than the
* default, in case it helps to track down bugs in the future.
*/
/* WMPLSetCaseSensitive(False); */
path = wdefaultspathfordomain("WindowMaker");
+1 -1
View File
@@ -103,7 +103,7 @@ int main(int argc, char **argv)
dict = WMReadPropListFromFile(path);
if (!dict) {
dict = WMCreatePLDictionary(key, value, NULL);
dict = WMCreatePLDictionary(key, value);
} else {
WMPutInPLDictionary(dict, key, value);
}
+2 -2
View File
@@ -1199,14 +1199,14 @@ static void changeTextureForWorkspace(const char *domain, char *texture, int wor
array = getValueForKey("WindowMaker", "WorkspaceSpecificBack");
if (!array) {
array = WMCreatePLArray(NULL, NULL);
array = WMCreateEmptyPLArray();
}
j = WMGetPropListItemCount(array);
if (workspace >= j) {
WMPropList *empty;
empty = WMCreatePLArray(NULL, NULL);
empty = WMCreateEmptyPLArray();
while (j++ < workspace - 1) {
WMAddToPLArray(array, empty);
+3 -3
View File
@@ -38,7 +38,7 @@ int main(int argc, char **argv)
else
ProgName++;
color_name = (char **)malloc(sizeof(char *) * argc);
color_name = (char **)wmalloc(sizeof(char *) * argc);
if (color_name == NULL) {
fprintf(stderr, "Cannot allocate memory!\n");
exit(1);
@@ -106,13 +106,13 @@ int main(int argc, char **argv)
exit(1);
}
colors = malloc(sizeof(RColor *) * (ncolors + 1));
colors = wmalloc(sizeof(RColor *) * (ncolors + 1));
for (i = 0; i < ncolors; i++) {
if (!XParseColor(dpy, ctx->cmap, color_name[i], &color)) {
printf("could not parse color \"%s\"\n", color_name[i]);
exit(1);
} else {
colors[i] = malloc(sizeof(RColor));
colors[i] = wmalloc(sizeof(RColor));
colors[i]->red = color.red >> 8;
colors[i]->green = color.green >> 8;
colors[i]->blue = color.blue >> 8;
+11
View File
@@ -5,3 +5,14 @@ edition = "2024"
[lib]
crate-type = ["staticlib"]
[build-dependencies]
cc = "1.0"
[dependencies]
atomic-write-file = "0.3"
hashbrown = "0.16.0"
libc = "0.2.175"
nom = "8.0"
nom-language = "0.1"
x11 = "2.21.0"
+6 -1
View File
@@ -2,9 +2,14 @@ AUTOMAKE_OPTIONS =
RUST_SOURCES = \
src/array.rs \
src/data.rs \
src/defines.c \
src/defines.rs \
src/find_file.rs \
src/hash_table.rs \
src/lib.rs \
src/memory.rs
src/memory.rs \
src/prop_list.rs
RUST_EXTRA = \
Cargo.lock \
+8
View File
@@ -0,0 +1,8 @@
use cc;
fn main() {
cc::Build::new()
.file("src/defines.c")
.compile("defines");
println!("cargo::rerun-if-changed=src/defines.c");
}
+13
View File
@@ -19,6 +19,19 @@ pub enum Format {
/// Rust.
pub struct Data(Rc<RefCell<Inner>>);
impl Data {
/// Runs `f` on a borrow of `self`'s data, returning whatever `f` does.
///
/// This is provided because the internal structure of `Data` does not make
/// it easy to borrow its contents directly. As we migrate away from the
/// original C interfaces to WINGs data structures, we may be able to
/// provide a direct borrow, which would make this method obsolete. (That
/// would be a good thing.)
pub fn with_bytes<R>(&self, f: impl FnOnce(&[u8]) -> R) -> R {
f(&self.0.borrow().bytes)
}
}
struct Inner {
bytes: Vec<u8>,
format: Format,
+5
View File
@@ -0,0 +1,5 @@
#include "../../config-paths.h"
const char *get_GSUSER_SUBDIR() {
return GSUSER_SUBDIR;
}
+45
View File
@@ -0,0 +1,45 @@
//! Lookup functions for preprocessor symbols.
//!
//! Functions in this module may be called to get the value of various
//! preprocessor symbols that are available on the C side of things.
//!
//! ## Rust rewrite notes
//!
//! Until we move away from autootols entirely, we might be stuck with this as
//! along as it makes sense to keep the configure script as the main entrypoint
//! for compile-time configuration.
use std::ffi::{c_char, CStr};
// Functions defined in src/defines.c.
unsafe extern "C" {
fn get_GSUSER_SUBDIR() -> *const c_char;
}
/// Returns the value of the GSUSER_SUBDIR preprocessor symbol defined at
/// Autotools configuration time prior to compilation. This is the final
/// component of the root path where user data files are stored (usually
/// `GNUstep`, as in `$HOME/GNUstep`). Returns `None` if this value cannot be
/// determined or is empty.
pub fn gsuser_subdir() -> Option<String> {
let s = unsafe { get_GSUSER_SUBDIR() };
if s.is_null() {
return None;
}
let s = unsafe { CStr::from_ptr(s) };
if s.is_empty() {
return None;
}
String::from_utf8(s.to_bytes().iter().copied().collect()).ok()
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn gsuser_subdir_is_set() {
let s = gsuser_subdir().unwrap();
assert!(!s.is_empty());
}
}
+184 -10
View File
@@ -12,21 +12,32 @@
//! whose first component is `~` will still be resolved relatively to the
//! current user's home directory.
//!
//! Keep in mind that these utilities are not strictly correct as originally
//! designed: a file path that appears valid when it is checked in a subroutine
//! may become invalid if the file is deleted between when the path is checked
//! and when downstream code attempts to open the file. A better design would
//! open the file and return a live file pointer instead of simply returning a
//! path that is likely to work. Future work should redesign this module to
//! avoid this issue.
//! ## Rust rewrite notes
//!
//! Many of these utilities are not strictly correct as originally designed: a
//! file path that appears valid when it is checked in a subroutine may become
//! invalid if the file is deleted between when the path is checked and when
//! downstream code attempts to open the file. (This is a TOCTOU issue.) A
//! better design would open the file and return a live file pointer instead of
//! simply returning a path that is likely to work. Future work should redesign
//! this module to avoid this issue.
use crate::defines;
use std::{
env,
ffi::OsStr,
ffi::{CStr, OsStr},
fs::File,
io,
path::{Component, Path, PathBuf},
};
/// Tries to interpret `s` as a UTF-8 path. Returns `None` if decoding `s`
/// fails.
pub fn path_from_cstr(s: &CStr) -> Option<PathBuf> {
s.to_str().ok().map(PathBuf::from)
}
/// If `file` is an absolute path can be opened, returns that path. Paths
/// starting with `~` are treated as absolute, and the user's home directory is
/// substituted for `~` (so `~/foo` becomes `(users's home directory)/foo`). If
@@ -78,13 +89,116 @@ pub fn in_paths<'a>(paths: impl Iterator<Item = &'a Path>, file: &Path) -> Optio
None
}
/// Returns the root path that user data will be stored at. This is probably
/// `$HOME/GNUstep` (or some other default if a different path was specified for
/// [`defaults::gsuser_subdir`] at compilation time), unless the environment
/// variable `WMAKER_USER_ROOT` is defined, in which case it's that.
pub fn user_gnustep_path() -> Option<PathBuf> {
match env::var("WMAKER_USER_ROOT") {
Ok(path) => {
if let Some(path) = absolute(&PathBuf::from(&path)) {
return Some(path);
}
}
Err(env::VarError::NotUnicode(_)) => {
// TODO: warn.
}
Err(env::VarError::NotPresent) => (),
}
match (env::home_dir(), defines::gsuser_subdir()) {
(None, _) => {
// TODO: warn.
None
}
(Some(mut parent), Some(subdir)) => {
parent.push(subdir);
Some(parent)
}
(Some(_), None) => {
// TODO: warn.
None
}
}
}
/// Creates the directory at `p` and all of its parents, respecting the current
/// umask as much as possible. Only allows creation of paths under
/// [`user_gnustep_path`].
pub fn create_path_hierarchy<P: AsRef<Path>>(p: P) -> io::Result<()> {
let p = p.as_ref();
let Ok(canonical_p) = p.canonicalize() else {
return Err(io::Error::other("cannot canonicalize requested path"));
};
let Some(wmaker_user_root) = user_gnustep_path() else {
return Err(io::Error::other("cannot determine WMAKER_USER_ROOT. try setting the environment variable WMAKER_USER_ROOT."));
};
let Ok(wmaker_user_root) = Path::new(&wmaker_user_root).canonicalize() else {
return Err(io::Error::other(
"cannot canonicalize path for WMAKER_USER_ROOT",
));
};
if !canonical_p.starts_with(&wmaker_user_root) {
return Err(io::Error::other(
"requested path is not under WMAKER_USER_ROOT",
));
}
create_path_hierarchy_impl(&canonical_p)
}
/// Removes the file at `p` (and anything under it, if `p` is a directory). Only
/// allows deletion of files under [`user_gnustep_path`]`/Defaults` or
/// [`user_gnustep_path`]`/Library`.
pub fn remove_path_hierarchy<P: AsRef<Path>>(p: P) -> io::Result<()> {
let p = p.as_ref();
let Ok(canonical_p) = p.canonicalize() else {
return Err(io::Error::other("cannot canonicalize requested path"));
};
let Some(wmaker_user_root) = user_gnustep_path() else {
return Err(io::Error::other("cannot determine WMAKER_USER_ROOT. try setting the environment variable WMAKER_USER_ROOT."));
};
let Ok(wmaker_user_root) = Path::new(&wmaker_user_root).canonicalize() else {
return Err(io::Error::other(
"cannot canonicalize path for WMAKER_USER_ROOT",
));
};
let mut defaults = wmaker_user_root.clone();
defaults.push("Defaults");
let mut library = wmaker_user_root;
library.push("Library");
if !canonical_p.starts_with(&defaults) && !canonical_p.starts_with(&library) {
return Err(io::Error::other(
"requested path is not under WMAKER_USER_ROOT/Defaults or WMAKER_USER_ROOT/Library",
));
}
std::fs::remove_dir_all(canonical_p)
}
#[cfg(target_family = "unix")]
fn create_path_hierarchy_impl(p: &Path) -> io::Result<()> {
use std::os::unix::fs::DirBuilderExt;
std::fs::DirBuilder::new()
.recursive(true)
.mode(0o777)
.create(p)
}
#[cfg(not(target_family = "unix"))]
fn create_path_hierarchy_impl(p: &Path) -> io::Result<()> {
std::fs::DirBuilder::new().recursive(true).create(p)
}
pub mod ffi {
use super::{absolute, in_paths};
use super::{absolute, create_path_hierarchy, in_paths, path_from_cstr, remove_path_hierarchy, user_gnustep_path};
use crate::memory::alloc_bytes;
use std::{
env,
ffi::{CStr, OsStr, c_char, c_int},
ffi::{c_char, c_int, CStr, OsStr},
iter,
os::unix::ffi::OsStrExt,
path::{Path, PathBuf},
@@ -218,4 +332,64 @@ pub mod ffi {
return -1;
}
}
/// Delegates to [`create_path_hierarchy`].
///
/// ## Rust rewrite notes
///
/// The original C implementation of this function stripped the last path
/// component if it did not end in a `'/'` (i.e., it looked like a regular
/// file instead of a directory). This behavior does not appear to be relied
/// upon by any existing callers except for `WMWritePropListToFile`, which
/// has been rewritten, and it is not super portable (and annoying because
/// it adds weird edge cases). So each component of `path` is treated as a
/// directory to be made.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wmkdirhier(path: *const c_char) -> c_int {
if path.is_null() {
return 0;
}
let path = unsafe { CStr::from_ptr(path) };
let Some(path) = path_from_cstr(path) else {
return 0;
};
if create_path_hierarchy(path).is_ok() {
return 1;
} else {
return 0;
}
}
/// Delegates to [`remove_path_hierarchy`].
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wrmdirhier(path: *const c_char) -> c_int {
if path.is_null() {
return 0;
}
let path = unsafe { CStr::from_ptr(path) };
let Some(path) = path_from_cstr(path) else {
return 0;
};
if remove_path_hierarchy(path).is_ok() {
return 1;
} else {
return 0;
}
}
/// Delegates to [`user_gnustep_path`]. Returns the path, which must be The
/// returned value must be freed with [`crate::memory::free_bytes`] or
/// [`crate::memory::ffi::wfree`], path cannot be determined.
///
/// ## Rust rewrite notes
///
/// This was originally in `WINGs/userdefaults.c`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wusergnusteppath() -> *mut c_char {
if let Some(path) = user_gnustep_path() {
to_c_str(&path)
} else {
ptr::null_mut()
}
}
}
+328
View File
@@ -0,0 +1,328 @@
use hashbrown::hash_map::{self, HashMap};
use std::{
borrow::Borrow,
ffi::{CStr, c_void},
hash::{Hash, Hasher},
mem,
};
pub enum HashTable {
PointerKeyed(HashMap<VoidPointer, VoidPointer>),
StringKeyed(HashMap<StringPointer, VoidPointer>),
}
impl HashTable {
pub fn new_pointer_keyed() -> Self {
HashTable::PointerKeyed(HashMap::new())
}
pub fn new_string_keyed() -> Self {
HashTable::StringKeyed(HashMap::new())
}
pub fn clear(&mut self) {
match self {
HashTable::PointerKeyed(m) => m.clear(),
HashTable::StringKeyed(m) => m.clear(),
}
}
pub fn len(&self) -> usize {
match self {
HashTable::PointerKeyed(m) => m.len(),
HashTable::StringKeyed(m) => m.len(),
}
}
pub unsafe fn get(&self, key: *const i8) -> Option<*mut i8> {
match self {
HashTable::PointerKeyed(m) => {
let key = key.cast_mut();
m.get(&key).map(|x| x.0)
}
HashTable::StringKeyed(m) => {
let key = StringPointer(key.cast_mut());
let v = m.get(&key).map(|x| x.0);
mem::forget(key);
v
}
}
}
pub unsafe fn insert(&mut self, key: *mut i8, data: VoidPointer) -> Option<VoidPointer> {
match self {
HashTable::PointerKeyed(m) => m.insert(VoidPointer(key), data),
HashTable::StringKeyed(m) => m.insert(StringPointer(key), data),
}
}
pub unsafe fn remove(&mut self, key: *const i8) {
match self {
HashTable::PointerKeyed(m) => {
let key = key.cast_mut();
m.remove(&key);
}
HashTable::StringKeyed(m) => {
let key = StringPointer(key.cast_mut());
m.remove(&key);
mem::forget(key);
}
}
}
}
#[derive(Debug, Eq, PartialEq, Hash)]
#[repr(transparent)]
pub struct VoidPointer(*mut i8);
impl Drop for VoidPointer {
fn drop(&mut self) {
unsafe { libc::free(self.0.cast::<c_void>()) }
}
}
impl Borrow<*mut i8> for VoidPointer {
fn borrow(&self) -> &*mut i8 {
&self.0
}
}
#[derive(Debug)]
#[repr(transparent)]
pub struct StringPointer(*mut i8);
impl PartialEq for StringPointer {
fn eq(&self, other: &Self) -> bool {
match (self.0.is_null(), other.0.is_null()) {
(true, true) => true,
(true, false) => false,
(false, true) => false,
(false, false) => unsafe { CStr::from_ptr(self.0) == CStr::from_ptr(other.0) },
}
}
}
impl Eq for StringPointer {}
impl Hash for StringPointer {
fn hash<H: Hasher>(&self, h: &mut H) {
if self.0.is_null() {
h.write_usize(0)
} else {
unsafe { CStr::from_ptr(self.0).hash(h) }
}
}
}
impl Drop for StringPointer {
fn drop(&mut self) {
unsafe {
libc::free(self.0.cast::<c_void>());
}
}
}
pub enum Enumerator<'a> {
PointerKeyed(hash_map::IterMut<'a, VoidPointer, VoidPointer>),
StringKeyed(hash_map::IterMut<'a, StringPointer, VoidPointer>),
}
pub mod ffi {
use std::{
ffi::{c_int, c_uint, c_void},
mem, ptr,
};
use super::{Enumerator, HashTable, StringPointer, VoidPointer};
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn WMCreateIdentityHashTable() -> *mut HashTable {
Box::leak(Box::new(HashTable::new_pointer_keyed()))
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn WMCreateStringHashTable() -> *mut HashTable {
Box::leak(Box::new(HashTable::new_string_keyed()))
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn WMFreeHashTable(table: *mut HashTable) {
if !table.is_null() {
let _ = unsafe { Box::from_raw(table) };
}
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn WMResetHashTable(table: *mut HashTable) {
if !table.is_null() {
unsafe {
(*table).clear();
}
}
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn WMCountHashTable(table: *mut HashTable) -> c_uint {
if table.is_null() {
0
} else {
(unsafe { (*table).len() }) as c_uint
}
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn WMHashGet(table: *mut HashTable, key: *const c_void) -> *mut c_void {
if table.is_null() {
return ptr::null_mut();
}
let key = key.cast::<i8>();
(unsafe { (*table).get(key) })
.map(|v| v.cast::<c_void>())
.unwrap_or(ptr::null_mut())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMHashGetItemAndKey(
table: *mut HashTable,
key: *const c_void,
value_dest: *mut *mut c_void,
key_dest: *mut *const c_void,
) -> c_int {
if table.is_null() {
return 0;
}
let table = unsafe { &mut *table };
match table {
HashTable::PointerKeyed(m) => {
let key = VoidPointer(key.cast::<i8>().cast_mut());
let result = match m.get_key_value_mut(&key) {
Some((k, v)) => {
unsafe {
*key_dest = k.0.cast::<c_void>();
*value_dest = v.0.cast::<c_void>();
}
1
}
None => 0,
};
mem::forget(key);
result
}
HashTable::StringKeyed(m) => {
let key = StringPointer(key.cast::<i8>().cast_mut());
let result = match m.get_key_value_mut(&key) {
Some((k, v)) => {
unsafe {
*key_dest = k.0.cast::<c_void>();
*value_dest = v.0.cast::<c_void>();
}
1
}
None => 0,
};
mem::forget(key);
return result;
}
}
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn WMHashInsert(
table: *mut HashTable,
key: *mut c_void,
data: *mut c_void,
) -> *mut c_void {
if table.is_null() {
return ptr::null_mut();
}
match unsafe { (*table).insert(key.cast::<i8>(), VoidPointer(data.cast::<i8>())) } {
Some(v) => {
let raw = v.0;
mem::forget(v);
raw.cast::<c_void>()
}
None => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn WMHashRemove(table: *mut HashTable, key: *mut c_void) {
if table.is_null() {
return;
}
unsafe {
(*table).remove(key.cast::<i8>());
}
}
/// Important note: this may leak memory if you don't pass the enumerator
/// back to [`WMFreeHashEnumerator`]. This is a breaking change from the
/// original C implementation, which did not require any resource cleanup.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMEnumerateHashTable(
table: *mut HashTable,
) -> *mut Enumerator<'static> {
if table.is_null() {
return ptr::null_mut();
}
let table = unsafe { &mut *table };
match table {
HashTable::PointerKeyed(m) => {
Box::leak(Box::new(Enumerator::PointerKeyed(m.iter_mut())))
}
HashTable::StringKeyed(m) => Box::leak(Box::new(Enumerator::StringKeyed(m.iter_mut()))),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMFreeHashEnumerator(e: *mut Enumerator<'static>) {
if !e.is_null() {
let _ = unsafe { Box::from_raw(e) };
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMNextHashEnumeratorItem(e: *mut Enumerator<'static>) -> *mut c_void {
if e.is_null() {
return ptr::null_mut();
}
let e = unsafe { &mut *e };
match e {
Enumerator::PointerKeyed(i) => match i.next() {
Some((_, v)) => v.0.cast::<c_void>(),
None => ptr::null_mut(),
},
Enumerator::StringKeyed(i) => match i.next() {
Some((_, v)) => v.0.cast::<c_void>(),
None => ptr::null_mut(),
},
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMNextHashEnumeratorKey(e: *mut Enumerator<'static>) -> *mut c_void {
if e.is_null() {
return ptr::null_mut();
}
let e = unsafe { &mut *e };
match e {
Enumerator::PointerKeyed(i) => match i.next() {
Some((k, _)) => k.0.cast::<c_void>(),
None => ptr::null_mut(),
},
Enumerator::StringKeyed(i) => match i.next() {
Some((k, _)) => k.0.cast::<c_void>(),
None => ptr::null_mut(),
},
}
}
}
+3
View File
@@ -1,4 +1,7 @@
pub mod array;
pub mod data;
pub mod defines;
pub mod find_file;
pub mod hash_table;
pub mod memory;
pub mod prop_list;
+79 -18
View File
@@ -18,12 +18,13 @@
//! memory than the baseline Window Maker code, it isn't really necessary in
//! this day and age.
use std::{alloc, mem, ptr::{self, NonNull}};
use std::{alloc, ffi::{c_char, CStr}, mem, ptr::{self, NonNull}};
/// Tracks the layout and reference count of an allocated chunk of memory.
#[derive(Clone, Copy)]
struct Header {
ptr: NonNull<u8>,
payload_size: usize,
layout: alloc::Layout,
refcount: u16,
}
@@ -49,21 +50,18 @@ pub fn alloc_bytes(size: usize) -> *mut u8 {
if size == 0 {
return ptr::null_mut();
}
let header_layout = match alloc::Layout::from_size_align(mem::size_of::<Header>(), 8) {
Ok(x) => x,
Err(_) => return ptr::null_mut(),
let Ok(header_layout) = alloc::Layout::from_size_align(mem::size_of::<Header>(), 8) else {
return ptr::null_mut();
};
let layout = match alloc::Layout::from_size_align(size, 8) {
Ok(x) => x,
Err(_) => return ptr::null_mut(),
let Ok(layout) = alloc::Layout::from_size_align(size, 8) else {
return ptr::null_mut();
};
let (layout, result_offset) = match header_layout.extend(layout) {
Ok(x) => x,
Err(_) => return ptr::null_mut(),
let Ok((full_layout, result_offset)) = header_layout.extend(layout) else {
return ptr::null_mut();
};
let full_segment = unsafe { alloc::alloc_zeroed(layout) };
let full_segment = unsafe { alloc::alloc_zeroed(full_layout) };
if full_segment.is_null() {
return ptr::null_mut();
}
@@ -76,7 +74,8 @@ pub fn alloc_bytes(size: usize) -> *mut u8 {
let header = result.sub(mem::size_of::<Header>()).cast::<Header>();
header.write_unaligned(Header {
ptr: NonNull::new_unchecked(full_segment),
layout: header_layout,
payload_size: size,
layout: full_layout,
refcount: 0,
});
}
@@ -84,6 +83,15 @@ pub fn alloc_bytes(size: usize) -> *mut u8 {
result
}
/// Allocates a segment with [`alloc_bytes`] and fills it with the contents of
/// `s`. The resulting string should be free'd by passing it to [`free_bytes`].
pub fn alloc_string(s: &CStr) -> *mut c_char {
let len = s.count_bytes() + 1;
let result = alloc_bytes(len).cast::<c_char>();
unsafe { ptr::copy_nonoverlapping(s.as_ptr().cast::<c_char>(), result, len); }
result
}
/// Frees the bytes pointed to by `b`.
///
/// ## Safety
@@ -105,7 +113,8 @@ pub mod ffi {
use std::{ffi::c_void, ptr};
/// Allocates `size` bytes. Returns null if `sizes is 0.
/// Allocates `size` bytes. Returns null if `size` is 0. Data will be
/// initialized but have an arbitrary value.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wmalloc(size: usize) -> *mut c_void {
alloc_bytes(size).cast::<c_void>()
@@ -118,16 +127,31 @@ pub mod ffi {
}
/// Resizes `ptr` to be at least `newsize` bytes in size, returning the
/// start of the new segment.
/// start of the new segment. If `newsize` is larger than `ptr`'s segment,
/// data in the new space will be initialized but have an arbitrary value.
///
/// ## Safety
///
/// Callers must ensure that `ptr` is a live allocation from [`wmalloc`] or [`wrealloc`].
/// If `ptr` is non-null, callers must ensure that it came from from
/// [`wmalloc`] or [`wrealloc`].
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wrealloc(ptr: *mut c_void, newsize: usize) -> *mut c_void {
if ptr.is_null() {
unsafe {
return wmalloc(newsize);
}
}
unsafe {
let result = wmalloc(newsize);
let result_header = ptr::read_unaligned(Header::for_alloc_bytes(result.cast()));
let ptr_header = ptr::read_unaligned(Header::for_alloc_bytes(ptr.cast()));
let copy_size = usize::min(
ptr_header.payload_size,
result_header.payload_size,
);
ptr::copy_nonoverlapping(ptr, result, copy_size);
wfree(ptr);
wmalloc(newsize).cast::<c_void>()
result
}
}
@@ -170,9 +194,9 @@ pub mod ffi {
#[cfg(test)]
mod test {
use super::{alloc_bytes, free_bytes, ffi::wrealloc, Header};
use super::{alloc_bytes, alloc_string, ffi::{wfree, wmalloc, wrealloc}, free_bytes, Header};
use std::{mem, os::raw::c_void, ptr};
use std::{ffi::CStr, mem, os::raw::c_void, ptr, slice};
#[test]
fn recover_header() {
@@ -209,6 +233,21 @@ mod test {
unsafe { free_bytes(x.cast::<u8>()); }
}
#[test]
fn multiple_allocs() {
unsafe {
let x = wmalloc(mem::size_of::<i64>());
*x.cast::<i64>() = 30;
let y = wmalloc(mem::size_of::<i32>());
*y.cast::<i32>() = 5;
let z = wmalloc(48);
*z.cast::<f32>() = 1.0;
wfree(x);
wfree(y);
wfree(z);
}
}
#[test]
fn realloc_nonzero() {
let x = alloc_bytes(mem::size_of::<i64>()).cast::<c_void>();
@@ -219,4 +258,26 @@ mod test {
assert_eq!(unsafe { *y }, 17);
unsafe { free_bytes(y.cast::<u8>()); }
}
#[test]
fn realloc_retains_data() {
let x: *mut u8 = unsafe { wmalloc(10).cast() };
unsafe {
let xs = slice::from_raw_parts_mut(&mut *x, 10);
// We know that xs should be zeroed.
assert_eq!(xs, &[0u8; 10]);
for i in 0u8..10 {
xs[i as usize] = i;
}
assert_eq!(xs, (0..10).collect::<Vec::<u8>>());
}
}
#[test]
fn alloc_free_string() {
let s = alloc_string(c"hello");
assert!(!s.is_null());
assert_eq!(unsafe { CStr::from_ptr(s) }, c"hello");
unsafe { free_bytes(s.cast::<u8>()); }
}
}
+863
View File
@@ -0,0 +1,863 @@
//! Property lists: shared, tree-structured data.
//!
//! ## Rust rewrite notes
//!
//! This implementation should be good enough to facilitate migrating from C to
//! Rust and trasitioning away from using property lists everywhere instead of
//! proper structs. `PropList`s are a really cool general-purpose tool for
//! attaching data to objects and persisting it to disk. But in Rust, it is
//! easier to use proper structs with typed fields and appropriate `#[derive]`
//! declarations to generate code for serialization and deserialization
//! (presumably using Serde).
//!
//! As code that uses `PropList`s is rewritte in Rust, we should work on
//! migrating away from use of `PropList`s. Objects whose fields that can be
//! statically typed should be represented as structs. They may still be
//! persisted by cramming them into `PropList`s and writing those to disk, but
//! it would be better still to implement Serde-based serialization to and from
//! the property list format.
//!
//! The `PropList` implementation itself can also be improved substantially. See
//! [`PropList`] for thoughts on this.
use atomic_write_file::unix::OpenOptionsExt;
use std::{
cell::RefCell,
collections::{hash_map, HashMap},
ffi::{CString, OsStr, OsString},
fmt, hash,
io::{self, BufWriter, Write},
path::Path,
process::Command,
ptr,
rc::Rc,
};
use crate::find_file;
pub mod parser;
pub mod writer;
/// Payload of a [`PropList`].
#[derive(Eq, PartialEq)]
pub enum Node {
/// Text data. This is UTF-8 encoded and null-safe.
///
/// ## Rust rewrite notes
///
/// It would be better for this to be a `String`, but the C interface
/// requires borrows of C-style strings.
String(CString),
/// Array of child `PropList`s.
Array(Vec<PropList>),
/// `PropList`-keyed table of child `PropList`s. Keys should only have
/// `Node::String` or `Node::Data` payloads, although there is almost no
/// enforcement of this.
Dictionary(HashMap<PropList, PropList>),
}
impl hash::Hash for Node {
fn hash<H: hash::Hasher>(&self, h: &mut H) {
match self {
Node::String(s) => s.hash(h),
Node::Array(a) => {
for p in a {
p.hash(h);
}
}
Node::Dictionary(d) => {
for (k, v) in d {
k.hash(h);
v.hash(h);
}
}
}
}
}
impl fmt::Debug for Node {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
writer::Display {
inline: writer::Inline::Soft,
clear_left: false,
indentation: 0,
increment: 2,
node: self,
}
)
}
}
fn merge_shallow(dest: PropList, source: PropList) {
if ptr::eq(dest.0.as_ref(), source.0.as_ref()) {
return;
}
let Node::Dictionary(ref mut dest_items) = *dest.0.borrow_mut() else {
return;
};
let Node::Dictionary(ref source) = *source.0.borrow() else {
return;
};
for (k, v) in source {
if ptr::eq(dest.0.as_ptr(), k.0.as_ptr()) {
// Don't borrow k if it is already borrowed as dest.
continue;
}
dest_items.insert(k.clone(), v.clone());
}
}
fn merge_deep(dest: PropList, source: PropList) {
if ptr::eq(dest.0.as_ptr(), source.0.as_ptr()) {
return;
}
let Node::Dictionary(dest_items) = &mut *dest.0.borrow_mut() else {
return;
};
let Node::Dictionary(source_items) = &*source.0.borrow() else {
return;
};
for (key, value) in source_items {
if key.0.try_borrow().is_err() || value.0.try_borrow().is_err() {
// Something has already borrowed key or value. This may happen if
// source contains pointers that are also in dest, or if dest is
// cyclic. This is bad, but we just bail out.
continue;
}
match dest_items.entry(key.clone()) {
hash_map::Entry::Vacant(v) => {
// Dest has nothing at key. Insert value from source.
v.insert(value.clone());
}
hash_map::Entry::Occupied(mut o) => {
let recur = match *o.get().0.borrow() {
Node::Dictionary(_) => true,
_ => false,
};
if recur {
// dest[key] is a dictionary. Recur on dest[key] and value from source.
merge_deep(o.get().clone(), value.clone());
} else {
// dest[key] is not a dictionary. Overwrite with value from source.
o.insert(value.clone());
}
}
}
}
}
fn subtract_shallow(dest: PropList, source: PropList) {
if ptr::eq(dest.0.as_ptr(), source.0.as_ptr()) {
if let Node::Dictionary(ref mut items) = *dest.0.borrow_mut() {
items.clear();
}
return;
}
let Node::Dictionary(ref mut dest_items) = *dest.0.borrow_mut() else {
return;
};
let Node::Dictionary(ref source_items) = *source.0.borrow() else {
return;
};
for (k, v) in source_items.iter() {
if ptr::eq(dest.0.as_ptr(), k.0.as_ptr()) {
continue;
}
if let hash_map::Entry::Occupied(o) = dest_items.entry(k.clone()) {
if o.get() == v {
o.remove();
}
}
}
}
fn subtract_deep(dest: PropList, source: PropList) {
if ptr::eq(dest.0.as_ptr(), source.0.as_ptr()) {
if let Node::Dictionary(ref mut items) = *dest.0.borrow_mut() {
items.clear();
}
return;
}
let Node::Dictionary(ref mut dest_items) = *dest.0.borrow_mut() else {
return;
};
let Node::Dictionary(ref source_items) = *source.0.borrow() else {
return;
};
for (k, v) in source_items.iter() {
if ptr::eq(dest.0.as_ptr(), k.0.as_ptr()) {
continue;
}
if let hash_map::Entry::Occupied(o) = dest_items.entry(k.clone()) {
if o.get() == v {
o.remove();
continue;
}
let recur = match (&*o.get().0.borrow(), &*v.0.borrow()) {
(Node::Dictionary(_), Node::Dictionary(_)) => true,
_ => false,
};
if recur {
subtract_deep(o.get().clone(), v.clone());
}
}
}
}
/// Data graph with convenient (de)serialization to/from the [property
/// list](https://en.wikipedia.org/wiki/Property_list) format.
///
/// ## Rust rewrite notes
///
/// The original WUtils `PropList` was a reference-counted pointer, so it
/// supported shallow copy and shared-memory semantics that we have continued to
/// try to support in the Rust implementation. As a result, `PropList` is a thin
/// wrapper around an `Rc<RefCell<Node>>`. There are several reasons why this is
/// probably unnecessary and something we should migrate away from:
///
/// * It allows for the creation of non-tree structures, which was probably
/// never intended. (A degenerate `PropList` could even have itself as a child.)
/// * It complicates recursive operations on `PropList`s (equality checks,
/// merging, or taking differences) because a given `PropList` may occur
/// multiple times when traversing two `PropList`s, but `Rc` only allows it to be
/// mutably borrowed once.
/// * Allowing subtrees to be shared between two different `PropList`s
/// may lead to spooky action at a distance and may not actually be taken
/// advantage of by any client code.
///
/// As client code is migrated into Rust, it would be great to move away from
/// this implementation to a simpler one. As discussed in the module-level
/// rewrite notes, we may even be able to do away with `PropList` itself
/// (perhaps in favor of using Serde to write to and from property list files on
/// disk).
#[derive(Clone)]
pub struct PropList(Rc<RefCell<Node>>);
impl PropList {
pub fn new(node: Node) -> Self {
PropList(Rc::new(RefCell::new(node)))
}
/// Reads `r` to the end and tries to parse it into a `PropList`.
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<PropList, String> {
let path = path.as_ref().to_path_buf();
let buf = std::fs::read_to_string(&path).map_err(|e| format!("{}", e))?;
parser::from_str(buf.as_str())
}
// Runs `command` and tries to parse a PropList from its standard output.
pub fn from_command<S: AsRef<OsStr>>(command: S) -> Result<PropList, String> {
let command: OsString = command.as_ref().to_os_string();
let output = Command::new("/bin/sh")
.arg("-c")
.arg(command.clone())
.output()
.map_err(|e| format!("{}", e))?;
let output = str::from_utf8(&output.stdout).map_err(|e| format!("{}", e))?;
parser::from_str(&output)
}
pub fn display_indented<'s>(&'s self) -> impl fmt::Display + 's {
writer::Display {
inline: writer::Inline::Soft,
clear_left: false,
indentation: 0,
increment: 2,
node: self.0.borrow(),
}
}
pub fn display_unindented<'s>(&'s self) -> impl fmt::Display + 's {
writer::Display {
inline: writer::Inline::Hard,
clear_left: false,
indentation: 0,
increment: 0,
node: self.0.borrow(),
}
}
}
impl Eq for PropList {}
impl PartialEq for PropList {
fn eq(&self, other: &Self) -> bool {
*self.0.borrow() == *other.0.borrow()
}
}
impl hash::Hash for PropList {
fn hash<H: hash::Hasher>(&self, h: &mut H) {
self.0.borrow().hash(h)
}
}
impl fmt::Debug for PropList {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
write!(out, "{:?}", self.0.borrow())?;
Ok(())
}
}
impl PropList {
pub fn deep_clone(&self) -> Self {
match &*self.0.borrow() {
Node::String(s) => PropList::new(Node::String(s.clone())),
Node::Array(items) => {
PropList::new(Node::Array(items.iter().map(|x| x.deep_clone()).collect()))
}
Node::Dictionary(items) => PropList::new(Node::Dictionary(
items
.iter()
.map(|(k, v)| (k.deep_clone(), v.deep_clone()))
.collect(),
)),
}
}
/// Atomically serialize this `PropList` to `path`, creating any necessary
/// parent directories.
///
/// `path` is written to atomically: either the serialized `PropList` will
/// be completely written to `path`, or the operation will fail and any
/// existing file at `path` will not be modified.
///
/// ## Rust rewrite notes
///
/// As originally noted in `proplist.c`, a Coverity security bug report
/// flagged the need to preserve the permissions on the file being written
/// to. This should be respected in the rewritten code under Unix-like
/// operataing systems.
pub fn write_to_file<P: AsRef<Path>>(&self, path: &P) -> io::Result<()> {
self.write_to_file_impl(path.as_ref())
}
fn write_to_file_impl(&self, path: &Path) -> io::Result<()> {
if let Some(parent) = path.parent() {
find_file::create_path_hierarchy(parent)?;
}
let file = atomic_write_file::AtomicWriteFile::options()
.preserve_mode(true)
.open(&path)?;
let mut out = BufWriter::new(file);
writeln!(&mut out, "{}", self.display_indented())?;
out.into_inner()?.commit()
}
}
pub mod ffi {
use crate::{find_file::path_from_cstr, memory};
use super::{
merge_deep, merge_shallow, parser, subtract_deep, subtract_shallow, Node, PropList,
};
use std::{
collections::HashMap, ffi::{c_char, c_int, c_uint, CStr, CString, OsString}, ptr, str::FromStr
};
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMCreatePLString(s: *const c_char) -> *mut PropList {
if s.is_null() {
return ptr::null_mut();
}
let s = unsafe { CStr::from_ptr(s) };
Box::leak(Box::new(PropList::new(Node::String(s.into()))))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMCreatePLArrayFromSlice(
elems: *mut PropList,
length: c_uint,
) -> *mut PropList {
if elems.is_null() {
return ptr::null_mut();
}
let elems = unsafe { &*ptr::slice_from_raw_parts(elems, length as usize) };
Box::leak(Box::new(PropList::new(Node::Array(
elems.iter().cloned().collect(),
))))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMCreateEmptyPLArray() -> *mut PropList {
Box::leak(Box::new(PropList::new(Node::Array(Vec::new()))))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMCreatePLDictionary(
key: *mut PropList,
value: *mut PropList,
) -> *mut PropList {
if key.is_null() || value.is_null() {
return Box::leak(Box::new(PropList::new(Node::Dictionary(HashMap::new()))));
}
let key = unsafe { (*key).clone() };
let value = unsafe { (*value).clone() };
Box::leak(Box::new(PropList::new(Node::Dictionary(
[(key, value)].into(),
))))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMCreateEmptyPLDictionary() -> *mut PropList {
Box::leak(Box::new(PropList::new(Node::Dictionary(HashMap::new()))))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMRetainPropList(plist: *mut PropList) -> *mut PropList {
if plist.is_null() {
return ptr::null_mut();
}
unsafe { Box::leak(Box::new((*plist).clone())) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMReleasePropList(plist: *mut PropList) {
if plist.is_null() {
return;
}
let _ = unsafe { ptr::read(plist) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMInsertInPLArray(
plist: *mut PropList,
index: c_int,
item: *mut PropList,
) {
if plist.is_null() || index < 0 || item.is_null() {
return;
}
let plist = unsafe { &mut *plist };
if let Node::Array(ref mut items) = *plist.0.borrow_mut() {
let item = unsafe { (*item).clone() };
items.insert(index as usize, item);
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMAddToPLArray(plist: *mut PropList, item: *mut PropList) {
if plist.is_null() || item.is_null() {
return;
}
let plist = unsafe { &mut *plist };
if let Node::Array(ref mut items) = *plist.0.borrow_mut() {
let item = unsafe { (*item).clone() };
items.push(item);
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMDeleteFromPLArray(plist: *mut PropList, index: c_int) {
if plist.is_null() || index < 0 {
return;
}
let plist = unsafe { &mut *plist };
if let Node::Array(ref mut items) = *plist.0.borrow_mut() {
items.remove(index as usize);
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMRemoveFromPLArray(plist: *mut PropList, item: *mut PropList) {
if plist.is_null() || item.is_null() {
return;
}
let plist = unsafe { &mut *plist };
let item = unsafe { &*item };
if let Node::Array(ref mut items) = *plist.0.borrow_mut() {
if let Some((i, _)) = items.iter().enumerate().find(|(_, x)| *x == item) {
items.remove(i);
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMPutInPLDictionary(
plist: *mut PropList,
key: *mut PropList,
value: *mut PropList,
) {
if plist.is_null() || key.is_null() || value.is_null() {
return;
}
let plist = unsafe { &mut *plist };
if let Node::Dictionary(ref mut items) = *plist.0.borrow_mut() {
let key = unsafe { (*key).clone() };
let value = unsafe { (*value).clone() };
items.insert(key, value);
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMRemoveFromPLDictionary(plist: *mut PropList, key: *mut PropList) {
if plist.is_null() || key.is_null() {
return;
}
let plist = unsafe { &mut *plist };
let key = unsafe { &*key };
if let Node::Dictionary(ref mut items) = *plist.0.borrow_mut() {
items.remove(key);
}
}
/// If `dest` and `source` are both dictionaries, overwrites entries in
/// `dest` with corresponding entries in `source`.
///
/// If `recursive` is non-zero, this is done recursively for values in
/// `dest` and `source` that are both dictionaries.
///
/// ## Rust rewrite notes
///
/// This operation is used a few times. It may be worth keeping around
/// longer-term, although it might be hard to express if we do transition
/// away from `PropList`s to statically typed struct trees.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMMergePLDictionaries(
dest: *mut PropList,
source: *mut PropList,
recursive: c_int,
) -> *mut PropList {
if dest.is_null() || ptr::eq(dest, source) || source.is_null() {
return dest;
}
let dest = unsafe { (*dest).clone() };
let source = unsafe { (*source).clone() };
if recursive == 0 {
merge_shallow(dest.clone(), source);
} else {
merge_deep(dest.clone(), source);
}
return Box::leak(Box::new(dest));
}
/// If `dest` and `source` are both dictionaries, removes from `dest` any
/// `(k, v)` pairs where `dest[k] == source[k]`.
///
/// If `recursive` is non-zero, this is done recursively over subtrees of
/// `dest` and `source` when both `dest` and `source` are dictionaries for
/// keys of `source` that are also keys of `dest`.
///
/// ## Rust rewrite notes
///
/// This operation is only used in one place. It may be better to implement
/// this behavior as a one-off closer to where it is used, or with a
/// different API differently (e.g., as a function of a more general
/// proplist diff).
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMSubtractPLDictionaries(
dest: *mut PropList,
source: *mut PropList,
recursive: c_int,
) -> *mut PropList {
if dest.is_null() {
return ptr::null_mut();
}
if source.is_null() {
return dest;
}
let dest = unsafe { (*dest).clone() };
let source = unsafe { (*source).clone() };
if recursive == 0 {
subtract_shallow(dest.clone(), source);
} else {
subtract_deep(dest.clone(), source);
}
Box::leak(Box::new(dest))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMGetPropListItemCount(plist: *mut PropList) -> c_int {
if plist.is_null() {
return 0;
}
let plist = unsafe { &*plist };
match &*plist.0.borrow() {
Node::Array(xs) => xs.len() as c_int,
Node::Dictionary(xs) => xs.len() as c_int,
_ => 0,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMIsPLString(plist: *mut PropList) -> c_int {
if plist.is_null() {
return 0;
}
let plist = unsafe { &*plist };
match &*plist.0.borrow() {
Node::String(_) => 1,
_ => 0,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMIsPLArray(plist: *mut PropList) -> c_int {
if plist.is_null() {
return 0;
}
let plist = unsafe { &*plist };
match &*plist.0.borrow() {
Node::Array(_) => 1,
_ => 0,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMIsPLDictionary(plist: *mut PropList) -> c_int {
if plist.is_null() {
return 0;
}
let plist = unsafe { &*plist };
match &*plist.0.borrow() {
Node::Dictionary(_) => 1,
_ => 0,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMIsPropListEqualTo(a: *mut PropList, b: *mut PropList) -> c_int {
if ptr::eq(a, b) {
return 1;
}
if a.is_null() {
return 0;
}
let a = unsafe { &*a };
let b = unsafe { &*b };
if a == b {
1
} else {
0
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMGetFromPLString(plist: *mut PropList) -> *const c_char {
if plist.is_null() {
return ptr::null_mut();
}
let plist = unsafe { &*plist };
if let Node::String(ref s) = *plist.0.borrow() {
s.as_ref().as_ptr().cast::<c_char>()
} else {
ptr::null()
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMGetFromPLArray(plist: *mut PropList, index: c_int) -> *mut PropList {
if plist.is_null() || index < 0 {
return ptr::null_mut();
}
let plist = unsafe { &*plist };
if let Node::Array(ref items) = *plist.0.borrow() {
if let Some(x) = items.get(index as usize) {
return Box::leak(Box::new(x.clone()));
}
}
ptr::null_mut()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMGetFromPLDictionary(plist: *mut PropList, key: *mut PropList) -> *mut PropList {
if plist.is_null() || key.is_null() {
return ptr::null_mut();
}
let plist = unsafe { &*plist };
let key = unsafe { &*key };
if let Node::Dictionary(ref items) = *plist.0.borrow() {
if let Some(item) = items.get(key) {
return Box::leak(Box::new(item.clone()));
}
}
ptr::null_mut()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMGetPLDictionaryKeys(plist: *mut PropList) -> *mut PropList {
if plist.is_null() {
return ptr::null_mut();
}
let plist = unsafe { &*plist };
if let Node::Dictionary(ref items) = *plist.0.borrow() {
return Box::leak(Box::new(PropList::new(Node::Array(items.keys().cloned().collect()))));
}
ptr::null_mut()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMDeepCopyPropList(plist: *mut PropList) -> *mut PropList {
if plist.is_null() {
return ptr::null_mut();
}
let plist = unsafe { &*plist };
Box::leak(Box::new(plist.deep_clone()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMCreatePropListFromDescription(desc: *const c_char) -> *mut PropList {
if desc.is_null() {
return ptr::null_mut();
}
let desc = unsafe { CStr::from_ptr(desc) };
let Ok(desc) = desc.to_str() else {
return ptr::null_mut();
};
match parser::from_str(desc) {
Ok(plist) => Box::leak(Box::new(plist)),
Err(_) => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMGetPropListDescription(
plist: *mut PropList,
indented: c_int,
) -> *mut c_char {
use std::io::Write;
if plist.is_null() {
return ptr::null_mut();
}
let plist = unsafe { &*plist };
let mut buf = Vec::new();
if indented != 0 {
if let Err(_) = write!(&mut buf, "{}", plist.display_indented()) {
return ptr::null_mut();
}
} else {
if let Err(_) = write!(&mut buf, "{}", plist.display_unindented()) {
return ptr::null_mut();
}
}
match CString::new(buf) {
Ok(s) => memory::alloc_string(s.as_c_str()),
Err(_) => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMReadPropListFromFile(path: *const c_char) -> *mut PropList {
if path.is_null() {
return ptr::null_mut();
}
let path = unsafe { CStr::from_ptr(path) };
let Ok(path) = path.to_str() else {
return ptr::null_mut();
};
match PropList::from_file(path) {
Ok(plist) => Box::leak(Box::new(plist)),
Err(_) => {
// TODO: print error message.
ptr::null_mut()
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMReadPropListFromPipe(command: *const c_char) -> *mut PropList {
if command.is_null() {
return ptr::null_mut();
}
let command = unsafe { CStr::from_ptr(command) };
let Ok(command) = command.to_str() else {
return ptr::null_mut();
};
let command = OsString::from_str(command).unwrap();
let Ok(output) = std::process::Command::new("/bin/sh")
.arg("-c")
.arg(command)
.output()
else {
// TODO: print error message.
return ptr::null_mut();
};
let Ok(output) = String::from_utf8(output.stdout) else {
// TODO: print error message.
return ptr::null_mut();
};
match parser::from_str(&output) {
Ok(plist) => Box::leak(Box::new(plist)),
Err(_) => {
// TODO: print error message.
ptr::null_mut()
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMWritePropListToFile(
plist: *mut PropList,
path: *const c_char,
) -> c_int {
if plist.is_null() || path.is_null() {
return 0;
}
let plist = unsafe {
&*plist
};
let path = unsafe {
CStr::from_ptr(path)
};
let Some(path) = path_from_cstr(path) else {
// TODO: complain.
return 0;
};
match plist.write_to_file(&path) {
Ok(_) => return 1,
Err(_) => {
// TODO: complain.
return 0;
}
}
}
}
#[cfg(test)]
mod test {
use std::ffi::CString;
use crate::memory;
use super::{Node, PropList, ffi};
#[test]
fn free_proplist_description() {
let mut plist = PropList::new(Node::Array(vec![PropList::new(Node::String(CString::from(c"hello"))),
PropList::new(Node::String(CString::from(c"world!")))]));
let desc = unsafe { ffi::WMGetPropListDescription(&mut plist, 1) };
assert!(!desc.is_null());
unsafe { memory::ffi::wfree(desc.cast()); }
}
#[test]
fn oob_array_access_returns_null() {
// This is the original WMArray behavior. I don't like it, but a bunch
// of existing code relies on it.
let mut list = PropList::new(Node::Array(vec![PropList::new(Node::String(CString::from(c"hello"))),
PropList::new(Node::String(CString::from(c"world!")))]));
assert!(unsafe { ffi::WMGetFromPLArray(&mut list, 3) }.is_null());
}
}
File diff suppressed because it is too large Load Diff
+667
View File
@@ -0,0 +1,667 @@
//! Rendering of [`super::PropList`]s to text.
//!
//! To use this module, call [`super::PropList::display_indented`] or
//! [`super::PropList::display_unindented`].
//!
//! ## Rust rewrite notes
//!
//! This should work well enough for writing Window Maker state to disk, but it
//! is not a perfect reimplementation of the behavior of classic WUtils PropList
//! serialization. In particular, the WUtils hashtable has a different key
//! iteration order than Rust's `HashMap`, so this library may write dictionary
//! entries to disk in a different order.
use super::{parser, Node};
use std::{
fmt::{self, Write},
ops::Deref,
};
/// Maximum width for a chunk of text being rendered with `Inline::Soft` before
/// falling back to `Inline::No`.
const SOFT_LINEBREAK_WIDTH: usize = 77;
/// Describes the line-breaking strategy for rendering a `Node`.
#[derive(Clone, Copy, Eq, PartialEq)]
pub(crate) enum Inline {
/// Do not attempt to render with no linebreaks.
No,
/// Attempt to render with no linebreaks, but fall back to `Inline::No` if
/// the result is too wide.
Soft,
/// Render with no linebreaks, regardless of output width.
Hard,
}
/// Bundles a reference to a `Node` with rendering instructions.
pub(crate) struct Display<N: Deref<Target = Node>> {
/// Line-breaking strategy.
pub(crate) inline: Inline,
/// Whether to render `node` as if it is the first item on a new line.
pub(crate) clear_left: bool,
/// Indentation level to render `node` at.
pub(crate) indentation: u32,
/// Amount by which `indentation` should increase when rendering children of
/// `node`.
pub(crate) increment: u32,
/// The `Node` to render.
pub(crate) node: N,
}
impl<N: Deref<Target = Node>> Display<N> {
/// Writes whitespace to `f` for the current level of indentation.
fn write_indent(&self, f: &mut fmt::Formatter) -> fmt::Result {
for _ in 0..self.indentation {
f.write_char(' ')?;
}
Ok(())
}
}
/// Writes `s` to `f`, backslash-escaping special characters as appropriate for
/// a quoted property list string.
fn write_escaped_string(s: &str, f: &mut fmt::Formatter) -> fmt::Result {
for c in s.chars() {
match c {
'\x07' => f.write_str(r#"\a"#)?,
'\x08' => f.write_str(r#"\b"#)?,
'\t' => f.write_str(r#"\t"#)?,
'\n' => f.write_str(r#"\n"#)?,
'\x0B' => f.write_str(r#"\v"#)?,
'\x0D' => f.write_str(r#"\f"#)?,
'\\' => f.write_str(r#"\\"#)?,
'"' => f.write_str(r#"\\""#)?,
c => f.write_char(c)?,
}
}
Ok(())
}
impl<N: Deref<Target = Node>> fmt::Display for Display<N> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.clear_left {
writeln!(f, "")?;
self.write_indent(f)?;
}
match &*self.node {
Node::Array(items) if self.inline != Inline::No => {
// Try to fit everything in one line.
let mut buf = String::new();
buf.push('(');
let mut items = items.iter();
if let Some(first) = items.next() {
write!(
&mut buf,
"{}",
Display {
inline: self.inline,
clear_left: false,
indentation: self.indentation,
increment: self.increment,
node: first.0.borrow(),
}
)?;
}
for next in items {
write!(
&mut buf,
", {}",
Display {
inline: self.inline,
clear_left: false,
indentation: self.indentation,
increment: self.increment,
node: next.0.borrow(),
}
)?;
}
buf.push(')');
if self.inline == Inline::Soft && buf.chars().count() > SOFT_LINEBREAK_WIDTH {
write!(
f,
"{}",
Display {
inline: Inline::No,
clear_left: false,
indentation: self.indentation,
increment: self.increment,
node: &*self.node,
}
)?;
} else {
f.write_str(&buf)?;
}
}
Node::Array(items) if items.is_empty() => f.write_str("()")?,
Node::Array(items) => {
f.write_char('(')?;
let mut items = items.iter();
if let Some(first) = items.next() {
write!(
f,
"{}",
Display {
inline: Inline::Soft,
clear_left: true,
indentation: self.indentation + self.increment,
increment: self.increment,
node: first.0.borrow(),
}
)?;
}
for next in items {
f.write_char(',')?;
write!(
f,
"{}",
Display {
inline: Inline::Soft,
clear_left: true,
indentation: self.indentation + self.increment,
increment: self.increment,
node: next.0.borrow(),
}
)?;
}
writeln!(f, "")?;
self.write_indent(f)?;
f.write_char(')')?;
}
Node::Dictionary(items) if items.is_empty() => write!(f, "{{}}")?,
Node::Dictionary(items) if self.inline != Inline::No => {
// Try to fit everything in one line.
let mut buf = String::new();
buf.push('{');
for (k, v) in items {
write!(
&mut buf,
" {} = {};",
Display {
inline: self.inline,
clear_left: false,
indentation: self.indentation,
increment: self.increment,
node: k.0.borrow(),
},
Display {
inline: self.inline,
clear_left: false,
indentation: self.indentation,
increment: self.increment,
node: v.0.borrow(),
}
)?;
}
write!(&mut buf, " }}")?;
if self.inline == Inline::Soft && buf.chars().count() > SOFT_LINEBREAK_WIDTH {
write!(
f,
"{}",
Display {
inline: Inline::No,
clear_left: false,
indentation: self.indentation,
increment: self.increment,
node: &*self.node,
}
)?;
} else {
f.write_str(&buf)?;
}
}
Node::Dictionary(items) => {
write!(f, "{{")?;
for (k, v) in items {
write!(
f,
"{} = {};",
Display {
inline: Inline::Soft,
clear_left: true,
indentation: self.indentation + self.increment,
increment: self.increment,
node: k.0.borrow(),
},
Display {
inline: Inline::Soft,
clear_left: false,
indentation: self.indentation + self.increment,
increment: self.increment,
node: v.0.borrow(),
}
)?;
}
writeln!(f, "")?;
self.write_indent(f)?;
f.write_char('}')?;
}
Node::String(s) => {
let mut unquoted = !s.is_empty();
for c in s.as_bytes() {
if !parser::is_unquoted_char(*c as char) {
unquoted = false;
break;
}
}
if unquoted {
write!(f, "{}", s.to_string_lossy())?;
} else {
f.write_char('"')?;
write_escaped_string(&s.to_string_lossy(), f)?;
f.write_char('"')?;
}
}
}
Ok(())
}
}
#[cfg(test)]
mod test {
use crate::prop_list::{Node, PropList};
use std::ffi::CString;
fn pl_array(xs: Vec<PropList>) -> PropList {
PropList::new(Node::Array(xs))
}
fn pl_string(s: &str) -> PropList {
PropList::new(Node::String(CString::new(s.as_bytes()).unwrap()))
}
#[test]
fn write_empty_string() {
let serialized = format!("{:?}", pl_string(""));
assert_eq!(serialized, r#""""#);
}
#[test]
fn write_unquoted_string() {
let serialized = format!("{:?}", pl_string("hello"));
assert_eq!(serialized, r#"hello"#);
}
#[test]
fn write_quoted_string() {
let serialized = format!("{:?}", pl_string("hello, world!"));
assert_eq!(serialized, r#""hello, world!""#);
}
#[test]
fn write_flat_array() {
let serialized = format!(
"{:?}",
pl_array(vec![pl_string("hello"), pl_string("world")])
);
assert_eq!(serialized, r#"(hello, world)"#);
}
#[test]
fn write_empty_array() {
let serialized = format!("{:?}", pl_array(vec![]));
assert_eq!(serialized, r#"()"#);
}
#[test]
fn write_nested_array() {
let serialized = format!(
"{:?}",
pl_array(vec![
pl_array(vec![pl_string("hello"), pl_string("world")]),
pl_string("baz"),
pl_string("quux plugh")
])
);
assert_eq!(serialized, r#"((hello, world), baz, "quux plugh")"#);
}
#[test]
fn write_long_nested_array() {
let serialized = format!(
"{:?}",
pl_array(vec![
pl_array(vec![pl_string("hello"), pl_string("world")]),
pl_string("baz"),
pl_string("quux plugh"),
pl_string("etc"),
pl_array(vec![
pl_string("lots"),
pl_string("of"),
pl_string("words"),
pl_string("go"),
pl_array(vec![
pl_string("here"),
pl_string("and"),
pl_string("stuff"),
pl_string("blah"),
pl_string("blah"),
pl_string("blah"),
])
])
])
);
assert_eq!(
serialized,
r#"(
(hello, world),
baz,
"quux plugh",
etc,
(lots, of, words, go, (here, and, stuff, blah, blah, blah))
)"#
);
}
#[test]
fn roundtrip_wm_state() {
let original = r#"{
Dock = {
AutoRaiseLower = No;
Applications = (
{
Forced = No;
Name = Logo.WMDock;
BuggyApplication = No;
AutoLaunch = No;
Position = "0,0";
Lock = Yes;
Command = "/usr/bin/WPrefs";
},
{
Forced = No;
Name = wmweather.wmweather;
DropCommand = "wmweather -s KBOS %d";
BuggyApplication = No;
AutoLaunch = Yes;
Position = "0,4";
Lock = Yes;
PasteCommand = "wmweather -s KBOS %s";
Command = "wmweather -s KBOS";
},
{
Forced = No;
Name = wmclock.WMClock;
DropCommand = "wmclock %d";
BuggyApplication = No;
AutoLaunch = Yes;
Position = "0,3";
Lock = Yes;
PasteCommand = "wmclock %s";
Command = wmclock;
},
{
Forced = No;
Name = emacs.Emacs;
DropCommand = "emacsclient -c %d";
BuggyApplication = No;
AutoLaunch = No;
Position = "0,2";
Lock = Yes;
PasteCommand = "emacsclient -c %s";
Command = "emacsclient -";
},
{
Forced = No;
Name = wmfire.wmfire;
DropCommand = "wmfire -m %d";
BuggyApplication = No;
AutoLaunch = Yes;
Position = "0,5";
Lock = Yes;
PasteCommand = "wmfire -m %s";
Command = "wmfire -m";
},
{
Forced = No;
Name = wmnet.WMNET;
DropCommand = "wmnet -W enp0s31f6 %d";
BuggyApplication = No;
AutoLaunch = Yes;
Position = "0,7";
Lock = Yes;
PasteCommand = "wmnet -W enp0s31f6 %s";
Command = "wmnet -W enp0s31f6";
},
{
Forced = No;
Name = wmtemp.DockApp;
DropCommand = "wmtemp -f %d";
BuggyApplication = No;
AutoLaunch = Yes;
Position = "0,6";
Lock = Yes;
PasteCommand = "wmtemp -f %s";
Command = "wmtemp -f";
},
{
Forced = No;
Name = firefox.Firefox;
DropCommand = "firefox %d";
BuggyApplication = No;
AutoLaunch = No;
Position = "0,1";
Lock = Yes;
PasteCommand = "firefox %s";
Command = firefox;
},
{
Forced = No;
Name = "org\\.gnome\\.Weather.org\\.gnome\\.Weather";
DropCommand = "org.gnome.Weather %d";
BuggyApplication = No;
AutoLaunch = No;
Position = "0,8";
Lock = Yes;
PasteCommand = "org.gnome.Weather %s";
Command = "/usr/bin/gnome-weather";
},
{
Forced = No;
Name = Zotero.Zotero;
DropCommand = "/home/stu/bin/zotero %d";
BuggyApplication = No;
AutoLaunch = No;
Position = "0,9";
Lock = Yes;
PasteCommand = "/home/stu/bin/zotero %s";
Command = "/home/stu/bin/zotero";
}
);
Lowered = Yes;
Position = "2496,0";
Applications1440 = (
{
Forced = No;
Name = Logo.WMDock;
BuggyApplication = No;
AutoLaunch = No;
Position = "0,0";
Lock = Yes;
Command = "/usr/bin/WPrefs";
},
{
Forced = No;
Name = wmweather.wmweather;
DropCommand = "wmweather -s KBOS %d";
BuggyApplication = No;
AutoLaunch = Yes;
Position = "0,4";
Lock = Yes;
PasteCommand = "wmweather -s KBOS %s";
Command = "wmweather -s KBOS";
},
{
Forced = No;
Name = wmclock.WMClock;
DropCommand = "wmclock %d";
BuggyApplication = No;
AutoLaunch = Yes;
Position = "0,3";
Lock = Yes;
PasteCommand = "wmclock %s";
Command = wmclock;
},
{
Forced = No;
Name = emacs.Emacs;
DropCommand = "emacsclient -c %d";
BuggyApplication = No;
AutoLaunch = No;
Position = "0,2";
Lock = Yes;
PasteCommand = "emacsclient -c %s";
Command = "emacsclient -";
},
{
Forced = No;
Name = wmfire.wmfire;
DropCommand = "wmfire -m %d";
BuggyApplication = No;
AutoLaunch = Yes;
Position = "0,5";
Lock = Yes;
PasteCommand = "wmfire -m %s";
Command = "wmfire -m";
},
{
Forced = No;
Name = wmnet.WMNET;
DropCommand = "wmnet -W enp0s31f6 %d";
BuggyApplication = No;
AutoLaunch = Yes;
Position = "0,7";
Lock = Yes;
PasteCommand = "wmnet -W enp0s31f6 %s";
Command = "wmnet -W enp0s31f6";
},
{
Forced = No;
Name = wmtemp.DockApp;
DropCommand = "wmtemp -f %d";
BuggyApplication = No;
AutoLaunch = Yes;
Position = "0,6";
Lock = Yes;
PasteCommand = "wmtemp -f %s";
Command = "wmtemp -f";
},
{
Forced = No;
Name = firefox.Firefox;
DropCommand = "firefox %d";
BuggyApplication = No;
AutoLaunch = No;
Position = "0,1";
Lock = Yes;
PasteCommand = "firefox %s";
Command = firefox;
},
{
Forced = No;
Name = "org\\.gnome\\.Weather.org\\.gnome\\.Weather";
DropCommand = "org.gnome.Weather %d";
BuggyApplication = No;
AutoLaunch = No;
Position = "0,8";
Lock = Yes;
PasteCommand = "org.gnome.Weather %s";
Command = "/usr/bin/gnome-weather";
},
{
Forced = No;
Name = Zotero.Zotero;
DropCommand = "/home/stu/bin/zotero %d";
BuggyApplication = No;
AutoLaunch = No;
Position = "0,9";
Lock = Yes;
PasteCommand = "/home/stu/bin/zotero %s";
Command = "/home/stu/bin/zotero";
}
);
};
Clip = {
Forced = No;
Name = Logo.WMClip;
DropCommand = "wmsetbg -u -t %d";
BuggyApplication = No;
AutoLaunch = No;
Position = "2496,1376";
Lock = No;
Command = "-";
};
Drawers = ();
Workspaces = (
{
Clip = {
AutoRaiseLower = No;
AutoCollapse = No;
Applications = (
{
Forced = No;
Name = xsnow.Xsnow;
DropCommand = "xsnow %d";
BuggyApplication = No;
AutoLaunch = No;
Position = "-2,0";
Lock = No;
PasteCommand = "xsnow %s";
Command = xsnow;
Omnipresent = No;
},
{
Forced = No;
Name = "thunderbird.Thunderbird-default";
DropCommand = "thunderbird %d";
BuggyApplication = No;
AutoLaunch = No;
Position = "-1,0";
Lock = No;
PasteCommand = "thunderbird %s";
Command = thunderbird;
Omnipresent = No;
}
);
Collapsed = No;
AutoAttractIcons = No;
Lowered = Yes;
};
Name = Mail;
},
{
Clip = {
AutoRaiseLower = No;
AutoCollapse = No;
Applications = ();
Collapsed = No;
AutoAttractIcons = No;
Lowered = Yes;
};
Name = Plans;
},
{
Clip = {
AutoRaiseLower = No;
AutoCollapse = No;
Applications = ();
Collapsed = No;
AutoAttractIcons = No;
Lowered = Yes;
};
Name = Misc;
},
);
}"#;
// We do (original serialized) -> plist -> (serialized) -> plist and
// compare the two plists because Rust's hashtable ordering (which is
// reflected in the serialized output) does not match the original
// WUtils hashtable ordering.
let original_plist = super::parser::from_str(original).unwrap();
let serialized = format!("{}", original_plist.display_indented());
let deserialized = super::parser::from_str(&serialized).unwrap();
assert_eq!(original_plist, deserialized);
}
}