diff --git a/Makefile.am b/Makefile.am index 8043b18b..0a50bf18 100644 --- a/Makefile.am +++ b/Makefile.am @@ -39,7 +39,7 @@ ACLOCAL_AMFLAGS = -I m4 AM_DISTCHECK_CONFIGURE_FLAGS = --enable-silent-rules LINGUAS='*' -SUBDIRS = wrlib WINGs wmaker-rs src util po WindowMaker wmlib WPrefs.app doc +SUBDIRS = wrlib wutil-rs WINGs wmaker-rs src util po WindowMaker wmlib WPrefs.app doc DIST_SUBDIRS = $(SUBDIRS) test EXTRA_DIST = TODO BUGS BUGFORM FAQ INSTALL \ diff --git a/WINGs/Makefile.am b/WINGs/Makefile.am index 60875dab..82ea4514 100644 --- a/WINGs/Makefile.am +++ b/WINGs/Makefile.am @@ -10,14 +10,17 @@ libWUtil_la_LDFLAGS = -version-info @WUTIL_VERSION@ lib_LTLIBRARIES = libWUtil.la libWINGs.la +wutilrs = $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a +wraster = $(top_builddir)/wrlib/libwraster.la -LDADD= libWUtil.la libWINGs.la $(top_builddir)/wrlib/libwraster.la @INTLIBS@ -libWINGs_la_LIBADD = libWUtil.la $(top_builddir)/wrlib/libwraster.la @XLIBS@ @XFT_LIBS@ @FCLIBS@ @LIBM@ @PANGO_LIBS@ -libWUtil_la_LIBADD = @LIBBSD@ +LDADD= libWUtil.la libWINGs.la $(wraster) $(wutilrs) @INTLIBS@ +libWINGs_la_LIBADD = libWUtil.la $(wraster) $(wutilrs) @XLIBS@ @XFT_LIBS@ @FCLIBS@ @LIBM@ @PANGO_LIBS@ +libWUtil_la_LIBADD = @LIBBSD@ $(wutilrs) EXTRA_DIST = BUGS make-rgb Examples Extras Tests + # wbutton.c libWINGs_la_SOURCES = \ configuration.c \ @@ -62,15 +65,11 @@ libWINGs_la_SOURCES = \ wwindow.c libWUtil_la_SOURCES = \ - array.c \ bagtree.c \ - data.c \ error.c \ error.h \ findfile.c \ handlers.c \ - hashtable.c \ - memory.c \ menuparser.c \ menuparser.h \ menuparser_macros.c \ diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index feff8ae8..18c8b52b 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -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; @@ -213,10 +209,6 @@ void wfree(void *ptr); void wrelease(void *ptr); void* wretain(void *ptr); -typedef void waborthandler(int); - -waborthandler* wsetabort(waborthandler* handler); - /* ---[ WINGs/error.c ]--------------------------------------------------- */ enum { @@ -248,11 +240,14 @@ char* wexpandpath(const char *path); int wcopy_file(const char *toPath, const char *srcFile, const char *destFile); -/* don't free the returned string */ -const char* wgethomedir(void); +/* You must free the returned string! */ +char* wgethomedir(void); /* ---[ WINGs/proplist.c ]------------------------------------------------ */ +/* + * Creates the directory path and all its parents. + */ int wmkdirhier(const char *path); int wrmdirhier(const char *path); @@ -342,7 +337,8 @@ void WHandleEvents(void); /* ---[ WINGs/hashtable.c ]----------------------------------------------- */ -WMHashTable* WMCreateHashTable(const WMHashTableCallbacks callbacks); +WMHashTable* WMCreateIdentityHashTable(); +WMHashTable* WMCreateStringHashTable(); void WMFreeHashTable(WMHashTable *table); @@ -392,15 +388,11 @@ 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 */ -/* ---[ WINGs/array.c ]--------------------------------------------------- */ +/* ---[ wutil-rs/src/array.rs ]--------------------------------------------------- */ /* * WMArray use an array to store the elements. @@ -422,29 +414,22 @@ WMArray* WMCreateArrayWithDestructor(int initialSize, WMFreeDataProc *destructor WMArray* WMCreateArrayWithArray(WMArray *array); -#define WMDuplicateArray(array) WMCreateArrayWithArray(array) - void WMEmptyArray(WMArray *array); void WMFreeArray(WMArray *array); int WMGetArrayItemCount(WMArray *array); -/* appends other to array. other remains unchanged */ -void WMAppendArray(WMArray *array, WMArray *other); - /* add will place the element at the end of the array */ void WMAddToArray(WMArray *array, void *item); /* insert will increment the index of elements after it by 1 */ void WMInsertInArray(WMArray *array, int index, void *item); -/* replace and set will return the old item WITHOUT calling the +/* set returns the old item WITHOUT calling the * destructor on it even if its available. Free the returned item yourself. */ -void* WMReplaceInArray(WMArray *array, int index, void *item); - -#define WMSetInArray(array, index, item) WMReplaceInArray(array, index, item) +void* WMSetInArray(WMArray *array, int index, void *item); /* delete and remove will remove the elements and cause the elements * after them to decrement their indexes by 1. Also will call the @@ -452,20 +437,21 @@ void* WMReplaceInArray(WMArray *array, int index, void *item); */ int WMDeleteFromArray(WMArray *array, int index); -#define WMRemoveFromArray(array, item) WMRemoveFromArrayMatching(array, NULL, item) +int WMRemoveFromArray(WMArray *array, void *item); int WMRemoveFromArrayMatching(WMArray *array, WMMatchDataProc *match, void *cdata); void* WMGetFromArray(WMArray *array, int index); -#define WMGetFirstInArray(array, item) WMFindInArray(array, NULL, item) - /* pop will return the last element from the array, also removing it * from the array. The destructor is NOT called, even if available. * Free the returned element if needed by yourself */ void* WMPopFromArray(WMArray *array); +/* Like WMFindInArray(array, NULL, item) */ +int WMGetFirstInArray(WMArray *array, void *item); + int WMFindInArray(WMArray *array, WMMatchDataProc *match, void *cdata); int WMCountInArray(WMArray *array, void *item); @@ -479,8 +465,6 @@ void WMSortArray(WMArray *array, WMCompareDataProc *comparer); void WMMapArray(WMArray *array, void (*function)(void*, void*), void *data); -WMArray* WMGetSubarrayWithRange(WMArray* array, WMRange aRange); - void* WMArrayFirst(WMArray *array, WMArrayIterator *iter); void* WMArrayLast(WMArray *array, WMArrayIterator *iter); @@ -604,37 +588,16 @@ WMData* WMCreateDataWithLength(unsigned length); WMData* WMCreateDataWithBytes(const void *bytes, unsigned length); -/* destructor is a function called to free the data when releasing the data - * object, or NULL if no freeing of data is necesary. */ -WMData* WMCreateDataWithBytesNoCopy(void *bytes, unsigned length, - WMFreeDataProc *destructor); - WMData* WMCreateDataWithData(WMData *aData); WMData* WMRetainData(WMData *aData); void WMReleaseData(WMData *aData); -/* Adjusting capacity */ - -void WMSetDataCapacity(WMData *aData, unsigned capacity); - -void WMSetDataLength(WMData *aData, unsigned length); - -void WMIncreaseDataLengthBy(WMData *aData, unsigned extraLength); - /* Accessing data */ const void* WMDataBytes(WMData *aData); -void WMGetDataBytes(WMData *aData, void *buffer); - -void WMGetDataBytesWithLength(WMData *aData, void *buffer, unsigned length); - -void WMGetDataBytesWithRange(WMData *aData, void *buffer, WMRange aRange); - -WMData* WMGetSubdataWithRange(WMData *aData, WMRange aRange); - /* Testing data */ Bool WMIsDataEqualToData(WMData *aData, WMData *anotherData); @@ -649,10 +612,6 @@ void WMAppendData(WMData *aData, WMData *anotherData); /* Modifying data */ -void WMReplaceDataBytesInRange(WMData *aData, WMRange aRange, const void *bytes); - -void WMResetDataBytesInRange(WMData *aData, WMRange aRange); - void WMSetData(WMData *aData, WMData *anotherData); @@ -756,25 +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); - -WMPropList* WMCreatePLDataWithBytesNoCopy(unsigned char *bytes, - unsigned int length, - WMFreeDataProc *destructor); +/* ---[ 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); @@ -812,8 +769,6 @@ int WMGetPropListItemCount(WMPropList *plist); Bool WMIsPLString(WMPropList *plist); -Bool WMIsPLData(WMPropList *plist); - Bool WMIsPLArray(WMPropList *plist); Bool WMIsPLDictionary(WMPropList *plist); @@ -823,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); @@ -838,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); diff --git a/WINGs/array.c b/WINGs/array.c deleted file mode 100644 index df52358d..00000000 --- a/WINGs/array.c +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Dynamically Resized Array - * - * Authors: Alfredo K. Kojima - * Dan Pascu - * - * This code is released to the Public Domain, but - * proper credit is always appreciated :) - */ - -#include -#include - -#include "WUtil.h" - -#define INITIAL_SIZE 8 -#define RESIZE_INCREMENT 8 - -typedef struct W_Array { - void **items; /* the array data */ - int itemCount; /* # of items in array */ - int allocSize; /* allocated size of array */ - WMFreeDataProc *destructor; /* the destructor to free elements */ -} W_Array; - -WMArray *WMCreateArray(int initialSize) -{ - return WMCreateArrayWithDestructor(initialSize, NULL); -} - -WMArray *WMCreateArrayWithDestructor(int initialSize, WMFreeDataProc * destructor) -{ - WMArray *array; - - array = wmalloc(sizeof(WMArray)); - - if (initialSize <= 0) { - initialSize = INITIAL_SIZE; - } - - array->items = wmalloc(sizeof(void *) * initialSize); - - array->itemCount = 0; - array->allocSize = initialSize; - array->destructor = destructor; - - return array; -} - -WMArray *WMCreateArrayWithArray(WMArray * array) -{ - WMArray *newArray; - - newArray = wmalloc(sizeof(WMArray)); - - newArray->items = wmalloc(sizeof(void *) * array->allocSize); - memcpy(newArray->items, array->items, sizeof(void *) * array->itemCount); - - newArray->itemCount = array->itemCount; - newArray->allocSize = array->allocSize; - newArray->destructor = NULL; - - return newArray; -} - -void WMEmptyArray(WMArray * array) -{ - if (array->destructor) { - while (array->itemCount > 0) { - array->itemCount--; - array->destructor(array->items[array->itemCount]); - } - } - /*memset(array->items, 0, array->itemCount * sizeof(void*)); */ - array->itemCount = 0; -} - -void WMFreeArray(WMArray * array) -{ - if (array == NULL) - return; - - WMEmptyArray(array); - wfree(array->items); - wfree(array); -} - -int WMGetArrayItemCount(WMArray * array) -{ - if (array == NULL) - return 0; - - return array->itemCount; -} - -void WMAppendArray(WMArray * array, WMArray * other) -{ - if (array == NULL || other == NULL) - return; - - if (other->itemCount == 0) - return; - - if (array->itemCount + other->itemCount > array->allocSize) { - array->allocSize += other->allocSize; - array->items = wrealloc(array->items, sizeof(void *) * array->allocSize); - } - - memcpy(array->items + array->itemCount, other->items, sizeof(void *) * other->itemCount); - array->itemCount += other->itemCount; -} - -void WMAddToArray(WMArray * array, void *item) -{ - if (array == NULL) - return; - - if (array->itemCount >= array->allocSize) { - array->allocSize += RESIZE_INCREMENT; - array->items = wrealloc(array->items, sizeof(void *) * array->allocSize); - } - array->items[array->itemCount] = item; - - array->itemCount++; -} - -void WMInsertInArray(WMArray * array, int index, void *item) -{ - if (array == NULL) - return; - - wassertr(index >= 0 && index <= array->itemCount); - - if (array->itemCount >= array->allocSize) { - array->allocSize += RESIZE_INCREMENT; - array->items = wrealloc(array->items, sizeof(void *) * array->allocSize); - } - if (index < array->itemCount) { - memmove(array->items + index + 1, array->items + index, - sizeof(void *) * (array->itemCount - index)); - } - array->items[index] = item; - - array->itemCount++; -} - -void *WMReplaceInArray(WMArray * array, int index, void *item) -{ - void *old; - - if (array == NULL) - return NULL; - - wassertrv(index >= 0 && index <= array->itemCount, NULL); - - /* is it really useful to perform append if index == array->itemCount ? -Dan */ - if (index == array->itemCount) { - WMAddToArray(array, item); - return NULL; - } - - old = array->items[index]; - array->items[index] = item; - - return old; -} - -int WMDeleteFromArray(WMArray * array, int index) -{ - if (array == NULL) - return 0; - - wassertrv(index >= 0 && index < array->itemCount, 0); - - if (array->destructor) { - array->destructor(array->items[index]); - } - - if (index < array->itemCount - 1) { - memmove(array->items + index, array->items + index + 1, - sizeof(void *) * (array->itemCount - index - 1)); - } - - array->itemCount--; - - return 1; -} - -int WMRemoveFromArrayMatching(WMArray * array, WMMatchDataProc * match, void *cdata) -{ - int i; - - if (array == NULL) - return 1; - - if (match != NULL) { - for (i = 0; i < array->itemCount; i++) { - if ((*match) (array->items[i], cdata)) { - WMDeleteFromArray(array, i); - return 1; - } - } - } else { - for (i = 0; i < array->itemCount; i++) { - if (array->items[i] == cdata) { - WMDeleteFromArray(array, i); - return 1; - } - } - } - - return 0; -} - -void *WMGetFromArray(WMArray * array, int index) -{ - if (index < 0 || array == NULL || index >= array->itemCount) - return NULL; - - return array->items[index]; -} - -void *WMPopFromArray(WMArray * array) -{ - if (array == NULL || array->itemCount <= 0) - return NULL; - - array->itemCount--; - - return array->items[array->itemCount]; -} - -int WMFindInArray(WMArray * array, WMMatchDataProc * match, void *cdata) -{ - int i; - - if (array == NULL) - return WANotFound; - - if (match != NULL) { - for (i = 0; i < array->itemCount; i++) { - if ((*match) (array->items[i], cdata)) - return i; - } - } else { - for (i = 0; i < array->itemCount; i++) { - if (array->items[i] == cdata) - return i; - } - } - - return WANotFound; -} - -int WMCountInArray(WMArray * array, void *item) -{ - int i, count; - - if (array == NULL) - return 0; - - for (i = 0, count = 0; i < array->itemCount; i++) { - if (array->items[i] == item) - count++; - } - - return count; -} - -void WMSortArray(WMArray * array, WMCompareDataProc * comparer) -{ - if (array == NULL) - return; - - if (array->itemCount > 1) { /* Don't sort empty or single element arrays */ - qsort(array->items, array->itemCount, sizeof(void *), comparer); - } -} - -void WMMapArray(WMArray * array, void (*function) (void *, void *), void *data) -{ - int i; - - if (array == NULL) - return; - - for (i = 0; i < array->itemCount; i++) { - (*function) (array->items[i], data); - } -} - -WMArray *WMGetSubarrayWithRange(WMArray * array, WMRange aRange) -{ - WMArray *newArray; - - if (aRange.count <= 0 || array == NULL) - return WMCreateArray(0); - - if (aRange.position < 0) - aRange.position = 0; - if (aRange.position >= array->itemCount) - aRange.position = array->itemCount - 1; - if (aRange.position + aRange.count > array->itemCount) - aRange.count = array->itemCount - aRange.position; - - newArray = WMCreateArray(aRange.count); - memcpy(newArray->items, array->items + aRange.position, sizeof(void *) * aRange.count); - newArray->itemCount = aRange.count; - - return newArray; -} - -void *WMArrayFirst(WMArray * array, WMArrayIterator * iter) -{ - if (array == NULL || array->itemCount == 0) { - *iter = WANotFound; - return NULL; - } else { - *iter = 0; - return array->items[0]; - } -} - -void *WMArrayLast(WMArray * array, WMArrayIterator * iter) -{ - if (array == NULL || array->itemCount == 0) { - *iter = WANotFound; - return NULL; - } else { - *iter = array->itemCount - 1; - return array->items[*iter]; - } -} - -void *WMArrayNext(WMArray * array, WMArrayIterator * iter) -{ - if (array == NULL) { - *iter = WANotFound; - return NULL; - } - - if (*iter >= 0 && *iter < array->itemCount - 1) { - return array->items[++(*iter)]; - } else { - *iter = WANotFound; - return NULL; - } -} - -void *WMArrayPrevious(WMArray * array, WMArrayIterator * iter) -{ - if (array == NULL) { - *iter = WANotFound; - return NULL; - } - - if (*iter > 0 && *iter < array->itemCount) { - return array->items[--(*iter)]; - } else { - *iter = WANotFound; - return NULL; - } -} diff --git a/WINGs/data.c b/WINGs/data.c deleted file mode 100644 index f4489099..00000000 --- a/WINGs/data.c +++ /dev/null @@ -1,289 +0,0 @@ -/* - * WINGs WMData function library - * - * Copyright (c) 1999-2003 Dan Pascu - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, - * MA 02110-1301, USA. - */ - -#include -#include "WUtil.h" - -typedef struct W_Data { - unsigned length; /* How many bytes we have */ - unsigned capacity; /* How many bytes it can hold */ - unsigned growth; /* How much to grow */ - void *bytes; /* Actual data */ - unsigned retainCount; - WMFreeDataProc *destructor; - int format; /* 0, 8, 16 or 32 */ -} W_Data; - -/* Creating and destroying data objects */ - -WMData *WMCreateDataWithCapacity(unsigned capacity) -{ - WMData *aData; - - aData = (WMData *) wmalloc(sizeof(WMData)); - - if (capacity > 0) - aData->bytes = wmalloc(capacity); - else - aData->bytes = NULL; - - aData->capacity = capacity; - aData->growth = capacity / 2 > 0 ? capacity / 2 : 1; - aData->length = 0; - aData->retainCount = 1; - aData->format = 0; - aData->destructor = wfree; - - return aData; -} - -WMData *WMCreateDataWithLength(unsigned length) -{ - WMData *aData; - - aData = WMCreateDataWithCapacity(length); - if (length > 0) { - aData->length = length; - } - - return aData; -} - -WMData *WMCreateDataWithBytes(const void *bytes, unsigned length) -{ - WMData *aData; - - aData = WMCreateDataWithCapacity(length); - aData->length = length; - memcpy(aData->bytes, bytes, length); - - return aData; -} - -WMData *WMCreateDataWithBytesNoCopy(void *bytes, unsigned length, WMFreeDataProc * destructor) -{ - WMData *aData; - - aData = (WMData *) wmalloc(sizeof(WMData)); - aData->length = length; - aData->capacity = length; - aData->growth = length / 2 > 0 ? length / 2 : 1; - aData->bytes = bytes; - aData->retainCount = 1; - aData->format = 0; - aData->destructor = destructor; - - return aData; -} - -WMData *WMCreateDataWithData(WMData * aData) -{ - WMData *newData; - - if (aData->length > 0) { - newData = WMCreateDataWithBytes(aData->bytes, aData->length); - } else { - newData = WMCreateDataWithCapacity(0); - } - newData->format = aData->format; - - return newData; -} - -WMData *WMRetainData(WMData * aData) -{ - aData->retainCount++; - return aData; -} - -void WMReleaseData(WMData * aData) -{ - aData->retainCount--; - if (aData->retainCount > 0) - return; - if (aData->bytes != NULL && aData->destructor != NULL) { - aData->destructor(aData->bytes); - } - wfree(aData); -} - -/* Adjusting capacity */ - -void WMSetDataCapacity(WMData * aData, unsigned capacity) -{ - if (aData->capacity != capacity) { - aData->bytes = wrealloc(aData->bytes, capacity); - aData->capacity = capacity; - aData->growth = capacity / 2 > 0 ? capacity / 2 : 1; - } - if (aData->length > capacity) { - aData->length = capacity; - } -} - -void WMSetDataLength(WMData * aData, unsigned length) -{ - if (length > aData->capacity) { - WMSetDataCapacity(aData, length); - } - if (length > aData->length) { - memset((unsigned char *)aData->bytes + aData->length, 0, length - aData->length); - } - aData->length = length; -} - -void WMSetDataFormat(WMData * aData, unsigned format) -{ - aData->format = format; -} - -void WMIncreaseDataLengthBy(WMData * aData, unsigned extraLength) -{ - WMSetDataLength(aData, aData->length + extraLength); -} - -/* Accessing data */ - -const void *WMDataBytes(WMData * aData) -{ - return aData->bytes; -} - -void WMGetDataBytes(WMData * aData, void *buffer) -{ - wassertr(aData->length > 0); - - memcpy(buffer, aData->bytes, aData->length); -} - -unsigned WMGetDataFormat(WMData * aData) -{ - return aData->format; -} - -void WMGetDataBytesWithLength(WMData * aData, void *buffer, unsigned length) -{ - wassertr(aData->length > 0); - wassertr(length <= aData->length); - - memcpy(buffer, aData->bytes, length); -} - -void WMGetDataBytesWithRange(WMData * aData, void *buffer, WMRange aRange) -{ - wassertr(aRange.position < aData->length); - wassertr(aRange.count <= aData->length - aRange.position); - - memcpy(buffer, (unsigned char *)aData->bytes + aRange.position, aRange.count); -} - -WMData *WMGetSubdataWithRange(WMData * aData, WMRange aRange) -{ - void *buffer; - WMData *newData; - - if (aRange.count <= 0) - return WMCreateDataWithCapacity(0); - - buffer = wmalloc(aRange.count); - WMGetDataBytesWithRange(aData, buffer, aRange); - newData = WMCreateDataWithBytesNoCopy(buffer, aRange.count, wfree); - newData->format = aData->format; - - return newData; -} - -/* Testing data */ - -Bool WMIsDataEqualToData(WMData * aData, WMData * anotherData) -{ - if (aData->length != anotherData->length) - return False; - else if (!aData->bytes && !anotherData->bytes) /* both are empty */ - return True; - else if (!aData->bytes || !anotherData->bytes) /* one of them is empty */ - return False; - return (memcmp(aData->bytes, anotherData->bytes, aData->length) == 0); -} - -unsigned WMGetDataLength(WMData * aData) -{ - return aData->length; -} - -/* Adding data */ -void WMAppendDataBytes(WMData * aData, const void *bytes, unsigned length) -{ - unsigned oldLength = aData->length; - unsigned newLength = oldLength + length; - - if (newLength > aData->capacity) { - unsigned nextCapacity = aData->capacity + aData->growth; - unsigned nextGrowth = aData->capacity ? aData->capacity : 1; - - while (nextCapacity < newLength) { - unsigned tmp = nextCapacity + nextGrowth; - - nextGrowth = nextCapacity; - nextCapacity = tmp; - } - WMSetDataCapacity(aData, nextCapacity); - aData->growth = nextGrowth; - } - memcpy((unsigned char *)aData->bytes + oldLength, bytes, length); - aData->length = newLength; -} - -void WMAppendData(WMData * aData, WMData * anotherData) -{ - if (anotherData->length > 0) - WMAppendDataBytes(aData, anotherData->bytes, anotherData->length); -} - -/* Modifying data */ - -void WMReplaceDataBytesInRange(WMData * aData, WMRange aRange, const void *bytes) -{ - wassertr(aRange.position < aData->length); - wassertr(aRange.count <= aData->length - aRange.position); - - memcpy((unsigned char *)aData->bytes + aRange.position, bytes, aRange.count); -} - -void WMResetDataBytesInRange(WMData * aData, WMRange aRange) -{ - wassertr(aRange.position < aData->length); - wassertr(aRange.count <= aData->length - aRange.position); - - memset((unsigned char *)aData->bytes + aRange.position, 0, aRange.count); -} - -void WMSetData(WMData * aData, WMData * anotherData) -{ - unsigned length = anotherData->length; - - WMSetDataCapacity(aData, length); - if (length > 0) - memcpy(aData->bytes, anotherData->bytes, length); - aData->length = length; -} - -/* Storing data */ diff --git a/WINGs/findfile.c b/WINGs/findfile.c index bb2ce7f0..10c26b3b 100644 --- a/WINGs/findfile.c +++ b/WINGs/findfile.c @@ -34,331 +34,6 @@ #include #include -#ifndef PATH_MAX -#define PATH_MAX 1024 -#endif - - -const char *wgethomedir(void) -{ - static char *home = NULL; - char *tmp; - struct passwd *user; - - if (home) - return home; - - tmp = GETENV("HOME"); - if (tmp) { - home = wstrdup(tmp); - return home; - } - - user = getpwuid(getuid()); - if (!user) { - werror(_("could not get password entry for UID %i"), getuid()); - home = "/"; - return home; - } - - if (!user->pw_dir) - home = "/"; - else - home = wstrdup(user->pw_dir); - - return home; -} - -/* - * Return the home directory for the specified used - * - * If user not found, returns NULL, otherwise always returns a path that is - * statically stored. - * - * Please note you must use the path before any other call to 'getpw*' or it - * may be erased. This is a design choice to avoid duplication considering - * the use case for this function. - */ -static const char *getuserhomedir(const char *username) -{ - static const char default_home[] = "/"; - struct passwd *user; - - user = getpwnam(username); - if (!user) { - werror(_("could not get password entry for user %s"), username); - return NULL; - } - if (!user->pw_dir) - return default_home; - else - return user->pw_dir; - -} - -char *wexpandpath(const char *path) -{ - const char *origpath = path; - char buffer2[PATH_MAX + 2]; - char buffer[PATH_MAX + 2]; - int i; - - memset(buffer, 0, PATH_MAX + 2); - - if (*path == '~') { - const char *home; - - path++; - if (*path == '/' || *path == 0) { - home = wgethomedir(); - if (strlen(home) > PATH_MAX || - wstrlcpy(buffer, home, sizeof(buffer)) >= sizeof(buffer)) - goto error; - } else { - int j; - j = 0; - while (*path != 0 && *path != '/') { - if (j > PATH_MAX) - goto error; - buffer2[j++] = *path; - buffer2[j] = 0; - path++; - } - home = getuserhomedir(buffer2); - if (!home || wstrlcat(buffer, home, sizeof(buffer)) >= sizeof(buffer)) - goto error; - } - } - - i = strlen(buffer); - - while (*path != 0 && i <= PATH_MAX) { - char *tmp; - - if (*path == '$') { - int j; - - path++; - /* expand $(HOME) or $HOME style environment variables */ - if (*path == '(') { - path++; - j = 0; - while (*path != 0 && *path != ')') { - if (j > PATH_MAX) - goto error; - buffer2[j++] = *(path++); - } - buffer2[j] = 0; - if (*path == ')') { - path++; - tmp = getenv(buffer2); - } else { - tmp = NULL; - } - if (!tmp) { - if ((i += strlen(buffer2) + 2) > PATH_MAX) - goto error; - buffer[i] = 0; - if (wstrlcat(buffer, "$(", sizeof(buffer)) >= sizeof(buffer) || - wstrlcat(buffer, buffer2, sizeof(buffer)) >= sizeof(buffer)) - goto error; - if (*(path-1)==')') { - if (++i > PATH_MAX || - wstrlcat(buffer, ")", sizeof(buffer)) >= sizeof(buffer)) - goto error; - } - } else { - if ((i += strlen(tmp)) > PATH_MAX || - wstrlcat(buffer, tmp, sizeof(buffer)) >= sizeof(buffer)) - goto error; - } - } else { - j = 0; - while (*path != 0 && *path != '/') { - if (j > PATH_MAX) - goto error; - buffer2[j++] = *(path++); - } - buffer2[j] = 0; - tmp = getenv(buffer2); - if (!tmp) { - if ((i += strlen(buffer2) + 1) > PATH_MAX || - wstrlcat(buffer, "$", sizeof(buffer)) >= sizeof(buffer) || - wstrlcat(buffer, buffer2, sizeof(buffer)) >= sizeof(buffer)) - goto error; - } else { - if ((i += strlen(tmp)) > PATH_MAX || - wstrlcat(buffer, tmp, sizeof(buffer)) >= sizeof(buffer)) - goto error; - } - } - } else { - buffer[i++] = *path; - path++; - } - } - - if (*path!=0) - goto error; - - return wstrdup(buffer); - -error: - errno = ENAMETOOLONG; - werror(_("could not expand %s"), origpath); - - return NULL; -} - -/* return address of next char != tok or end of string whichever comes first */ -static const char *skipchar(const char *string, char tok) -{ - while (*string != 0 && *string == tok) - string++; - - return string; -} - -/* return address of next char == tok or end of string whichever comes first */ -static const char *nextchar(const char *string, char tok) -{ - while (*string != 0 && *string != tok) - string++; - - return string; -} - -/* - *---------------------------------------------------------------------- - * findfile-- - * Finds a file in a : separated list of paths. ~ expansion is also - * done. - * - * Returns: - * The complete path for the file (in a newly allocated string) or - * NULL if the file was not found. - * - * Side effects: - * A new string is allocated. It must be freed later. - * - *---------------------------------------------------------------------- - */ -char *wfindfile(const char *paths, const char *file) -{ - char *path; - const char *tmp, *tmp2; - int len, flen; - char *fullpath; - - if (!file) - return NULL; - - if (*file == '/' || *file == '~' || *file == '$' || !paths || *paths == 0) { - if (access(file, F_OK) < 0) { - fullpath = wexpandpath(file); - if (!fullpath) - return NULL; - - if (access(fullpath, F_OK) < 0) { - wfree(fullpath); - return NULL; - } else { - return fullpath; - } - } else { - return wstrdup(file); - } - } - - flen = strlen(file); - tmp = paths; - while (*tmp) { - tmp = skipchar(tmp, ':'); - if (*tmp == 0) - break; - tmp2 = nextchar(tmp, ':'); - len = tmp2 - tmp; - path = wmalloc(len + flen + 2); - path = memcpy(path, tmp, len); - path[len] = 0; - if (path[len - 1] != '/' && - wstrlcat(path, "/", len + flen + 2) >= len + flen + 2) { - wfree(path); - return NULL; - } - - if (wstrlcat(path, file, len + flen + 2) >= len + flen + 2) { - wfree(path); - return NULL; - } - - fullpath = wexpandpath(path); - wfree(path); - - if (fullpath) { - if (access(fullpath, F_OK) == 0) { - return fullpath; - } - wfree(fullpath); - } - tmp = tmp2; - } - - return NULL; -} - -char *wfindfileinlist(char *const *path_list, const char *file) -{ - int i; - char *path; - int len, flen; - char *fullpath; - - if (!file) - return NULL; - - if (*file == '/' || *file == '~' || !path_list) { - if (access(file, F_OK) < 0) { - fullpath = wexpandpath(file); - if (!fullpath) - return NULL; - - if (access(fullpath, F_OK) < 0) { - wfree(fullpath); - return NULL; - } else { - return fullpath; - } - } else { - return wstrdup(file); - } - } - - flen = strlen(file); - for (i = 0; path_list[i] != NULL; i++) { - len = strlen(path_list[i]); - path = wmalloc(len + flen + 2); - path = memcpy(path, path_list[i], len); - path[len] = 0; - if (wstrlcat(path, "/", len + flen + 2) >= len + flen + 2 || - wstrlcat(path, file, len + flen + 2) >= len + flen + 2) { - wfree(path); - return NULL; - } - /* expand tilde */ - fullpath = wexpandpath(path); - wfree(path); - if (fullpath) { - /* check if file exists */ - if (access(fullpath, F_OK) == 0) { - return fullpath; - } - wfree(fullpath); - } - } - - return NULL; -} char *wfindfileinarray(WMPropList *array, const char *file) { @@ -419,105 +94,3 @@ char *wfindfileinarray(WMPropList *array, const char *file) } return NULL; } - -int wcopy_file(const char *dest_dir, const char *src_file, const char *dest_file) -{ - char *path_dst; - int fd_src, fd_dst; - struct stat stat_src; - mode_t permission_dst; - const size_t buffer_size = 2 * 1024 * 1024; /* 4MB is a decent start choice to allow the OS to take advantage of modern disk's performance */ - char *buffer; /* The buffer is not created on the stack to avoid possible stack overflow as our buffer is big */ - - try_again_src: - fd_src = open(src_file, O_RDONLY | O_NOFOLLOW); - if (fd_src == -1) { - if (errno == EINTR) - goto try_again_src; - werror(_("Could not open input file \"%s\": %s"), src_file, strerror(errno)); - return -1; - } - - /* Only accept to copy regular files */ - if (fstat(fd_src, &stat_src) != 0 || !S_ISREG(stat_src.st_mode)) { - close(fd_src); - return -1; - } - - path_dst = wstrconcat(dest_dir, dest_file); - try_again_dst: - fd_dst = open(path_dst, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); - if (fd_dst == -1) { - if (errno == EINTR) - goto try_again_dst; - werror(_("Could not create target file \"%s\": %s"), path_dst, strerror(errno)); - wfree(path_dst); - close(fd_src); - return -1; - } - - buffer = malloc(buffer_size); /* Don't use wmalloc to avoid the memset(0) we don't need */ - if (buffer == NULL) { - werror(_("could not allocate memory for the copy buffer")); - close(fd_dst); - goto cleanup_and_return_failure; - } - - for (;;) { - ssize_t size_data; - const char *write_ptr; - size_t write_remain; - - try_again_read: - size_data = read(fd_src, buffer, buffer_size); - if (size_data == 0) - break; /* End of File have been reached */ - if (size_data < 0) { - if (errno == EINTR) - goto try_again_read; - werror(_("could not read from file \"%s\": %s"), src_file, strerror(errno)); - close(fd_dst); - goto cleanup_and_return_failure; - } - - write_ptr = buffer; - write_remain = size_data; - while (write_remain > 0) { - ssize_t write_done; - - try_again_write: - write_done = write(fd_dst, write_ptr, write_remain); - if (write_done < 0) { - if (errno == EINTR) - goto try_again_write; - werror(_("could not write data to file \"%s\": %s"), path_dst, strerror(errno)); - close(fd_dst); - goto cleanup_and_return_failure; - } - write_ptr += write_done; - write_remain -= write_done; - } - } - - /* Keep only the permission-related part of the field: */ - permission_dst = stat_src.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX); - if (fchmod(fd_dst, permission_dst) != 0) - wwarning(_("could not set permission 0%03o on file \"%s\": %s"), - permission_dst, path_dst, strerror(errno)); - - if (close(fd_dst) != 0) { - werror(_("could not close the file \"%s\": %s"), path_dst, strerror(errno)); - cleanup_and_return_failure: - free(buffer); - close(fd_src); - unlink(path_dst); - wfree(path_dst); - return -1; - } - - free(buffer); - wfree(path_dst); - close(fd_src); - - return 0; -} diff --git a/WINGs/handlers.c b/WINGs/handlers.c index 88682867..c937aaa1 100644 --- a/WINGs/handlers.c +++ b/WINGs/handlers.c @@ -126,7 +126,7 @@ WMHandlerID WMAddTimerHandler(int milliseconds, WMCallback * callback, void *cda { TimerHandler *handler; - handler = malloc(sizeof(TimerHandler)); + handler = wmalloc(sizeof(TimerHandler)); if (!handler) return NULL; @@ -214,7 +214,7 @@ WMHandlerID WMAddIdleHandler(WMCallback * callback, void *cdata) { IdleHandler *handler; - handler = malloc(sizeof(IdleHandler)); + handler = wmalloc(sizeof(IdleHandler)); if (!handler) return NULL; @@ -279,7 +279,7 @@ Bool W_CheckIdleHandlers(void) return (idleHandler != NULL && WMGetArrayItemCount(idleHandler) > 0); } - handlerCopy = WMDuplicateArray(idleHandler); + handlerCopy = WMCreateArrayWithArray(idleHandler); WM_ITERATE_ARRAY(handlerCopy, handler, iter) { /* check if the handler still exist or was removed by a callback */ @@ -429,7 +429,7 @@ Bool W_HandleInputEvents(Bool waitForInput, int inputfd) count = poll(fds, nfds + extrafd, timeout); if (count > 0 && nfds > 0) { - WMArray *handlerCopy = WMDuplicateArray(inputHandler); + WMArray *handlerCopy = WMCreateArrayWithArray(inputHandler); int mask; /* use WM_ITERATE_ARRAY() here */ @@ -527,7 +527,7 @@ Bool W_HandleInputEvents(Bool waitForInput, int inputfd) count = select(1 + maxfd, &rset, &wset, &eset, timeoutPtr); if (count > 0 && nfds > 0) { - WMArray *handlerCopy = WMDuplicateArray(inputHandler); + WMArray *handlerCopy = WMCreateArrayWithArray(inputHandler); int mask; /* use WM_ITERATE_ARRAY() here */ diff --git a/WINGs/hashtable.c b/WINGs/hashtable.c deleted file mode 100644 index 2620c784..00000000 --- a/WINGs/hashtable.c +++ /dev/null @@ -1,422 +0,0 @@ -#include - -#include -#include -#include -#include - -#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 -}; diff --git a/WINGs/memory.c b/WINGs/memory.c deleted file mode 100644 index e05e17a8..00000000 --- a/WINGs/memory.c +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Window Maker miscelaneous function library - * - * Copyright (c) 1997-2003 Alfredo K. Kojima - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, - * MA 02110-1301, USA. - */ - -#include "wconfig.h" -#include "WUtil.h" - -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_STDNORETURN -#include -#endif - -#ifdef USE_BOEHM_GC -#ifndef GC_DEBUG -#define GC_DEBUG -#endif /* !GC_DEBUG */ -#include -#endif /* USE_BOEHM_GC */ - -#ifndef False -# define False 0 -#endif -#ifndef True -# define True 1 -#endif - -static void defaultHandler(int bla) -{ - if (bla) - kill(getpid(), SIGABRT); - else - exit(1); -} - -static waborthandler *aborthandler = defaultHandler; - -static inline noreturn void wAbort(int bla) -{ - (*aborthandler)(bla); - exit(-1); -} - -waborthandler *wsetabort(waborthandler * handler) -{ - waborthandler *old = aborthandler; - - aborthandler = handler; - - return old; -} - -static int Aborting = 0; /* if we're in the middle of an emergency exit */ - -static WMHashTable *table = NULL; - -void *wmalloc(size_t size) -{ - void *tmp; - - assert(size > 0); - -#ifdef USE_BOEHM_GC - tmp = GC_MALLOC(size); -#else - tmp = malloc(size); -#endif - if (tmp == NULL) { - wwarning("malloc() failed. Retrying after 2s."); - sleep(2); -#ifdef USE_BOEHM_GC - tmp = GC_MALLOC(size); -#else - tmp = malloc(size); -#endif - if (tmp == NULL) { - if (Aborting) { - fputs("Really Bad Error: recursive malloc() failure.", stderr); - exit(-1); - } else { - wfatal("virtual memory exhausted"); - Aborting = 1; - wAbort(False); - } - } - } - if (tmp != NULL) - memset(tmp, 0, size); - return tmp; -} - -void *wrealloc(void *ptr, size_t newsize) -{ - void *nptr; - - if (!ptr) { - nptr = wmalloc(newsize); - } else if (newsize == 0) { - wfree(ptr); - nptr = NULL; - } else { -#ifdef USE_BOEHM_GC - nptr = GC_REALLOC(ptr, newsize); -#else - nptr = realloc(ptr, newsize); -#endif - if (nptr == NULL) { - wwarning("realloc() failed. Retrying after 2s."); - sleep(2); -#ifdef USE_BOEHM_GC - nptr = GC_REALLOC(ptr, newsize); -#else - nptr = realloc(ptr, newsize); -#endif - if (nptr == NULL) { - if (Aborting) { - fputs("Really Bad Error: recursive realloc() failure.", stderr); - exit(-1); - } else { - wfatal("virtual memory exhausted"); - Aborting = 1; - wAbort(False); - } - } - } - } - return nptr; -} - -void *wretain(void *ptr) -{ - int *refcount; - - if (!table) { - table = WMCreateHashTable(WMIntHashCallbacks); - } - - refcount = WMHashGet(table, ptr); - if (!refcount) { - refcount = wmalloc(sizeof(int)); - *refcount = 1; - WMHashInsert(table, ptr, refcount); -#ifdef VERBOSE - printf("== %i (%p)\n", *refcount, ptr); -#endif - } else { - (*refcount)++; -#ifdef VERBOSE - printf("+ %i (%p)\n", *refcount, ptr); -#endif - } - - return ptr; -} - -void wfree(void *ptr) -{ - if (ptr) -#ifdef USE_BOEHM_GC - /* This should eventually be removed, once the criss-cross - * of wmalloc()d memory being free()d, malloc()d memory being - * wfree()d, various misuses of calling wfree() on objects - * allocated by libc malloc() and calling libc free() on - * objects allocated by Boehm GC (think external libraries) - * is cleaned up. - */ - if (GC_base(ptr) != 0) - GC_FREE(ptr); - else - free(ptr); -#else - free(ptr); -#endif - ptr = NULL; -} - -void wrelease(void *ptr) -{ - int *refcount; - - refcount = WMHashGet(table, ptr); - if (!refcount) { - wwarning("trying to release unexisting data %p", ptr); - } else { - (*refcount)--; - if (*refcount < 1) { -#ifdef VERBOSE - printf("RELEASING %p\n", ptr); -#endif - WMHashRemove(table, ptr); - wfree(refcount); - wfree(ptr); - } -#ifdef VERBOSE - else { - printf("- %i (%p)\n", *refcount, ptr); - } -#endif - } -} diff --git a/WINGs/menuparser.c b/WINGs/menuparser.c index 6d8411cc..d2070dd5 100644 --- a/WINGs/menuparser.c +++ b/WINGs/menuparser.c @@ -536,12 +536,14 @@ found_end_define_fname: while (*src != '\0') { idx = 0; if (*src == '~') { - const char *home = wgethomedir(); + char *home_head = wgethomedir(); + char *home = home_head;; while (*home != '\0') { if (idx < sizeof(buffer) - 2) buffer[idx++] = *home; home++; } + wfree(home_head); src++; } diff --git a/WINGs/notification.c b/WINGs/notification.c index b1b37128..d43fef54 100644 --- a/WINGs/notification.c +++ b/WINGs/notification.c @@ -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) diff --git a/WINGs/proplist.c b/WINGs/proplist.c index e7a764df..4a574cd6 100644 --- a/WINGs/proplist.c +++ b/WINGs/proplist.c @@ -1,975 +1,21 @@ - -#include -#include - -#include -#include -#include #include -#include -#include -#include -#include -#include #include "WUtil.h" -#include "wconfig.h" - -typedef enum { - WPLString = 0x57504c01, - WPLData = 0x57504c02, - WPLArray = 0x57504c03, - WPLDictionary = 0x57504c04 -} WPLType; - -typedef struct W_PropList { - WPLType type; - - union { - char *string; - WMData *data; - WMArray *array; - WMHashTable *dict; - } d; - - int retainCount; -} W_PropList; - -typedef struct PLData { - const char *ptr; - int pos; - const char *filename; - int lineNumber; -} PLData; - -typedef struct StringBuffer { - char *str; - int size; -} StringBuffer; - -static unsigned hashPropList(const void *param); -static WMPropList *getPLString(PLData * pldata); -static WMPropList *getPLQString(PLData * pldata); -static WMPropList *getPLData(PLData * pldata); -static WMPropList *getPLArray(PLData * pldata); -static WMPropList *getPLDictionary(PLData * pldata); -static WMPropList *getPropList(PLData * pldata); - -typedef Bool(*isEqualFunc) (const void *, const void *); - -static const WMHashTableCallbacks WMPropListHashCallbacks = { - hashPropList, - (isEqualFunc) WMIsPropListEqualTo, - NULL, - NULL -}; - -static Bool caseSensitive = True; - -#define BUFFERSIZE 8192 -#define BUFFERSIZE_INCREMENT 1024 - -#if 0 -# define DPUT(s) puts(s) -#else -# define DPUT(s) -#endif - -#define COMPLAIN(pld, msg) wwarning(_("syntax error in %s %s, line %i: %s"),\ - (pld)->filename ? "file" : "PropList",\ - (pld)->filename ? (pld)->filename : "description",\ - (pld)->lineNumber, msg) - -#define ISSTRINGABLE(c) (isalnum(c) || (c)=='.' || (c)=='_' || (c)=='/' \ - || (c)=='+') - -#define CHECK_BUFFER_SIZE(buf, ptr) \ - if ((ptr) >= (buf).size-1) {\ - (buf).size += BUFFERSIZE_INCREMENT;\ - (buf).str = wrealloc((buf).str, (buf).size);\ - } - -#define inrange(ch, min, max) ((ch)>=(min) && (ch)<=(max)) -#define noquote(ch) (inrange(ch, 'a', 'z') || inrange(ch, 'A', 'Z') || inrange(ch, '0', '9') || ((ch)=='_') || ((ch)=='.') || ((ch)=='$')) -#define charesc(ch) (inrange(ch, 0x07, 0x0c) || ((ch)=='"') || ((ch)=='\\')) -#define numesc(ch) (((ch)<=0x06) || inrange(ch, 0x0d, 0x1f) || ((ch)>0x7e)) -#define ishexdigit(ch) (inrange(ch, 'a', 'f') || inrange(ch, 'A', 'F') || inrange(ch, '0', '9')) -#define char2num(ch) (inrange(ch,'0','9') ? ((ch)-'0') : (inrange(ch,'a','f') ? ((ch)-0x57) : ((ch)-0x37))) -#define num2char(num) ((num) < 0xa ? ((num)+'0') : ((num)+0x57)) - -#define MaxHashLength 64 - -static unsigned hashPropList(const void *param) -{ - WMPropList *plist= (WMPropList *) param; - unsigned ret = 0; - unsigned ctr = 0; - const char *key; - int i, len; - - switch (plist->type) { - case WPLString: - key = plist->d.string; - len = WMIN(strlen(key), MaxHashLength); - for (i = 0; i < len; i++) { - ret ^= tolower(key[i]) << ctr; - ctr = (ctr + 1) % sizeof(char *); - } - /*while (*key) { - ret ^= tolower(*key++) << ctr; - ctr = (ctr + 1) % sizeof (char *); - } */ - break; - - case WPLData: - key = WMDataBytes(plist->d.data); - len = WMIN(WMGetDataLength(plist->d.data), MaxHashLength); - for (i = 0; i < len; i++) { - ret ^= key[i] << ctr; - ctr = (ctr + 1) % sizeof(char *); - } - break; - - default: - wwarning(_("Only string or data is supported for a proplist dictionary key")); - wassertrv(False, 0); - break; - } - - return ret; -} - -static WMPropList *retainPropListByCount(WMPropList * plist, int count) -{ - WMPropList *key, *value; - WMHashEnumerator e; - int i; - - plist->retainCount += count; - - switch (plist->type) { - case WPLString: - case WPLData: - break; - case WPLArray: - for (i = 0; i < WMGetArrayItemCount(plist->d.array); i++) { - retainPropListByCount(WMGetFromArray(plist->d.array, i), count); - } - break; - case WPLDictionary: - e = WMEnumerateHashTable(plist->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&value, (void **)&key)) { - retainPropListByCount(key, count); - retainPropListByCount(value, count); - } - break; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertrv(False, NULL); - break; - } - - return plist; -} - -static void releasePropListByCount(WMPropList * plist, int count) -{ - WMPropList *key, *value; - WMHashEnumerator e; - int i; - - plist->retainCount -= count; - - switch (plist->type) { - case WPLString: - if (plist->retainCount < 1) { - wfree(plist->d.string); - wfree(plist); - } - break; - case WPLData: - if (plist->retainCount < 1) { - WMReleaseData(plist->d.data); - wfree(plist); - } - break; - case WPLArray: - for (i = 0; i < WMGetArrayItemCount(plist->d.array); i++) { - releasePropListByCount(WMGetFromArray(plist->d.array, i), count); - } - if (plist->retainCount < 1) { - WMFreeArray(plist->d.array); - wfree(plist); - } - break; - case WPLDictionary: - e = WMEnumerateHashTable(plist->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&value, (void **)&key)) { - releasePropListByCount(key, count); - releasePropListByCount(value, count); - } - if (plist->retainCount < 1) { - WMFreeHashTable(plist->d.dict); - wfree(plist); - } - break; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertr(False); - break; - } -} - -static char *dataDescription(WMPropList * plist) -{ - const unsigned char *data; - char *retVal; - int i, j, length; - - data = WMDataBytes(plist->d.data); - length = WMGetDataLength(plist->d.data); - - retVal = (char *)wmalloc(2 * length + length / 4 + 3); - - retVal[0] = '<'; - for (i = 0, j = 1; i < length; i++) { - retVal[j++] = num2char((data[i] >> 4) & 0x0f); - retVal[j++] = num2char(data[i] & 0x0f); - if ((i & 0x03) == 3 && i != length - 1) { - /* if we've just finished a 32-bit int, add a space */ - retVal[j++] = ' '; - } - } - retVal[j++] = '>'; - retVal[j] = '\0'; - - return retVal; -} - -static char *stringDescription(WMPropList * plist) -{ - const char *str; - char *retVal, *sPtr, *dPtr; - int len, quote; - unsigned char ch; - - str = plist->d.string; - - if (strlen(str) == 0) { - return wstrdup("\"\""); - } - - /* FIXME: make this work with unichars. */ - - quote = 0; - sPtr = (char *)str; - len = 0; - while ((ch = *sPtr)) { - if (!noquote(ch)) { - quote = 1; - if (charesc(ch)) - len++; - else if (numesc(ch)) - len += 3; - } - sPtr++; - len++; - } - - if (quote) - len += 2; - - retVal = (char *)wmalloc(len + 1); - - sPtr = (char *)str; - dPtr = retVal; - - if (quote) - *dPtr++ = '"'; - - while ((ch = *sPtr)) { - if (charesc(ch)) { - *(dPtr++) = '\\'; - switch (ch) { - case '\a': - *dPtr = 'a'; - break; - case '\b': - *dPtr = 'b'; - break; - case '\t': - *dPtr = 't'; - break; - case '\n': - *dPtr = 'n'; - break; - case '\v': - *dPtr = 'v'; - break; - case '\f': - *dPtr = 'f'; - break; - default: - *dPtr = ch; /* " or \ */ - } - } else if (numesc(ch)) { - *(dPtr++) = '\\'; - *(dPtr++) = '0' + ((ch >> 6) & 07); - *(dPtr++) = '0' + ((ch >> 3) & 07); - *dPtr = '0' + (ch & 07); - } else { - *dPtr = ch; - } - sPtr++; - dPtr++; - } - - if (quote) - *dPtr++ = '"'; - - *dPtr = '\0'; - - return retVal; -} - -static char *description(WMPropList * plist) -{ - WMPropList *key, *val; - char *retstr = NULL; - char *str, *tmp, *skey, *sval; - WMHashEnumerator e; - int i; - - switch (plist->type) { - case WPLString: - retstr = stringDescription(plist); - break; - case WPLData: - retstr = dataDescription(plist); - break; - case WPLArray: - retstr = wstrdup("("); - for (i = 0; i < WMGetArrayItemCount(plist->d.array); i++) { - str = description(WMGetFromArray(plist->d.array, i)); - if (i == 0) { - retstr = wstrappend(retstr, str); - } else { - tmp = (char *)wmalloc(strlen(retstr) + strlen(str) + 3); - sprintf(tmp, "%s, %s", retstr, str); - wfree(retstr); - retstr = tmp; - } - wfree(str); - } - retstr = wstrappend(retstr, ")"); - break; - case WPLDictionary: - retstr = wstrdup("{"); - e = WMEnumerateHashTable(plist->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&val, (void **)&key)) { - skey = description(key); - sval = description(val); - tmp = (char *)wmalloc(strlen(retstr) + strlen(skey) + strlen(sval) + 5); - sprintf(tmp, "%s%s = %s;", retstr, skey, sval); - wfree(skey); - wfree(sval); - wfree(retstr); - retstr = tmp; - } - retstr = wstrappend(retstr, "}"); - break; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertrv(False, NULL); - break; - } - - return retstr; -} - -static char *indentedDescription(WMPropList * plist, int level) -{ - WMPropList *key, *val; - char *retstr = NULL; - char *str, *tmp, *skey, *sval; - WMHashEnumerator e; - int i; - - if (plist->type == WPLArray /* || plist->type==WPLDictionary */ ) { - retstr = description(plist); - - if (retstr && ((2 * (level + 1) + strlen(retstr)) <= 77)) { - return retstr; - } else if (retstr) { - wfree(retstr); - retstr = NULL; - } - } - - switch (plist->type) { - case WPLString: - retstr = stringDescription(plist); - break; - case WPLData: - retstr = dataDescription(plist); - break; - case WPLArray: - retstr = wstrdup("(\n"); - for (i = 0; i < WMGetArrayItemCount(plist->d.array); i++) { - str = indentedDescription(WMGetFromArray(plist->d.array, i), level + 1); - if (i == 0) { - tmp = (char *)wmalloc(2 * (level + 1) + strlen(retstr) + strlen(str) + 1); - sprintf(tmp, "%s%*s%s", retstr, 2 * (level + 1), "", str); - wfree(retstr); - retstr = tmp; - } else { - tmp = (char *)wmalloc(2 * (level + 1) + strlen(retstr) + strlen(str) + 3); - sprintf(tmp, "%s,\n%*s%s", retstr, 2 * (level + 1), "", str); - wfree(retstr); - retstr = tmp; - } - wfree(str); - } - tmp = (char *)wmalloc(strlen(retstr) + 2 * level + 3); - sprintf(tmp, "%s\n%*s)", retstr, 2 * level, ""); - wfree(retstr); - retstr = tmp; - break; - case WPLDictionary: - retstr = wstrdup("{\n"); - e = WMEnumerateHashTable(plist->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&val, (void **)&key)) { - skey = indentedDescription(key, level + 1); - sval = indentedDescription(val, level + 1); - tmp = (char *)wmalloc(2 * (level + 1) + strlen(retstr) + strlen(skey) - + strlen(sval) + 6); - sprintf(tmp, "%s%*s%s = %s;\n", retstr, 2 * (level + 1), "", skey, sval); - wfree(skey); - wfree(sval); - wfree(retstr); - retstr = tmp; - } - tmp = (char *)wmalloc(strlen(retstr) + 2 * level + 2); - sprintf(tmp, "%s%*s}", retstr, 2 * level, ""); - wfree(retstr); - retstr = tmp; - break; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertrv(False, NULL); - break; - } - - return retstr; -} - -static inline int getChar(PLData * pldata) -{ - int c; - - c = pldata->ptr[pldata->pos]; - if (c == 0) { - return 0; - } - - pldata->pos++; - - if (c == '\n') - pldata->lineNumber++; - - return c; -} - -static inline int getNonSpaceChar(PLData * pldata) -{ - int c; - - while (1) { - c = pldata->ptr[pldata->pos]; - if (c == 0) { - break; - } - pldata->pos++; - if (c == '\n') { - pldata->lineNumber++; - } else if (!isspace(c)) { - break; - } - } - - return c; -} - -static char *unescapestr(const char *src) -{ - char *dest = wmalloc(strlen(src) + 1); - char *dPtr; - char ch; - - for (dPtr = dest; ; dPtr++) { - ch = *src++; - if (ch == '\0') - break; - else if (ch != '\\') - *dPtr = ch; - else { - ch = *(src++); - if (ch == '\0') { - *dPtr = '\\'; - break; - } else if ((ch >= '0') && (ch <= '7')) { - char wch; - - /* Convert octal number to character */ - wch = (ch & 07); - ch = *src; - if ((ch >= '0') && (ch <= '7')) { - src++; - wch = (wch << 3) | (ch & 07); - ch = *src; - if ((ch >= '0') && (ch <= '7')) { - src++; - wch = (wch << 3) | (ch & 07); - } - } - *dPtr = wch; - } else { - switch (ch) { - case 'a': - *dPtr = '\a'; - break; - case 'b': - *dPtr = '\b'; - break; - case 't': - *dPtr = '\t'; - break; - case 'r': - *dPtr = '\r'; - break; - case 'n': - *dPtr = '\n'; - break; - case 'v': - *dPtr = '\v'; - break; - case 'f': - *dPtr = '\f'; - break; - default: - *dPtr = ch; - } - } - } - } - - *dPtr = 0; - - return dest; -} - -static WMPropList *getPLString(PLData * pldata) -{ - WMPropList *plist; - StringBuffer sBuf; - int ptr = 0; - int c; - - sBuf.str = wmalloc(BUFFERSIZE); - sBuf.size = BUFFERSIZE; - - while (1) { - c = getChar(pldata); - if (ISSTRINGABLE(c)) { - CHECK_BUFFER_SIZE(sBuf, ptr); - sBuf.str[ptr++] = c; - } else { - if (c != 0) { - pldata->pos--; - } - break; - } - } - - sBuf.str[ptr] = 0; - - if (ptr == 0) { - plist = NULL; - } else { - char *tmp = unescapestr(sBuf.str); - plist = WMCreatePLString(tmp); - wfree(tmp); - } - - wfree(sBuf.str); - - return plist; -} - -static WMPropList *getPLQString(PLData * pldata) -{ - WMPropList *plist; - int ptr = 0, escaping = 0, ok = 1; - int c; - StringBuffer sBuf; - - sBuf.str = wmalloc(BUFFERSIZE); - sBuf.size = BUFFERSIZE; - - while (1) { - c = getChar(pldata); - if (!escaping) { - if (c == '\\') { - escaping = 1; - continue; - } else if (c == '"') { - break; - } - } else { - CHECK_BUFFER_SIZE(sBuf, ptr); - sBuf.str[ptr++] = '\\'; - escaping = 0; - } - - if (c == 0) { - COMPLAIN(pldata, _("unterminated PropList string")); - ok = 0; - break; - } else { - CHECK_BUFFER_SIZE(sBuf, ptr); - sBuf.str[ptr++] = c; - } - } - - sBuf.str[ptr] = 0; - - if (!ok) { - plist = NULL; - } else { - char *tmp = unescapestr(sBuf.str); - plist = WMCreatePLString(tmp); - wfree(tmp); - } - - wfree(sBuf.str); - - return plist; -} - -static WMPropList *getPLData(PLData * pldata) -{ - int ok = 1; - int len = 0; - int c1, c2; - unsigned char buf[BUFFERSIZE], byte; - WMPropList *plist; - WMData *data; - - data = WMCreateDataWithCapacity(0); - - while (1) { - c1 = getNonSpaceChar(pldata); - if (c1 == 0) { - COMPLAIN(pldata, _("unterminated PropList data")); - ok = 0; - break; - } else if (c1 == '>') { - break; - } else if (ishexdigit(c1)) { - c2 = getNonSpaceChar(pldata); - if (c2 == 0 || c2 == '>') { - COMPLAIN(pldata, _("unterminated PropList data (missing hexdigit)")); - ok = 0; - break; - } else if (ishexdigit(c2)) { - byte = char2num(c1) << 4; - byte |= char2num(c2); - buf[len++] = byte; - if (len == sizeof(buf)) { - WMAppendDataBytes(data, buf, len); - len = 0; - } - } else { - COMPLAIN(pldata, _("non hexdigit character in PropList data")); - ok = 0; - break; - } - } else { - COMPLAIN(pldata, _("non hexdigit character in PropList data")); - ok = 0; - break; - } - } - - if (!ok) { - WMReleaseData(data); - return NULL; - } - - if (len > 0) - WMAppendDataBytes(data, buf, len); - - plist = WMCreatePLData(data); - WMReleaseData(data); - - return plist; -} - -static WMPropList *getPLArray(PLData * pldata) -{ - Bool first = True; - int ok = 1; - int c; - WMPropList *array, *obj; - - array = WMCreatePLArray(NULL); - - while (1) { - c = getNonSpaceChar(pldata); - if (c == 0) { - COMPLAIN(pldata, _("unterminated PropList array")); - ok = 0; - break; - } else if (c == ')') { - break; - } else if (c == ',') { - /* continue normally */ - } else if (!first) { - COMPLAIN(pldata, _("missing or unterminated PropList array")); - ok = 0; - break; - } else { - pldata->pos--; - } - first = False; - - obj = getPropList(pldata); - if (!obj) { - COMPLAIN(pldata, _("could not get PropList array element")); - ok = 0; - break; - } - WMAddToPLArray(array, obj); - WMReleasePropList(obj); - } - - if (!ok) { - WMReleasePropList(array); - array = NULL; - } - - return array; -} - -static WMPropList *getPLDictionary(PLData * pldata) -{ - int ok = 1; - int c; - WMPropList *dict, *key, *value; - - dict = WMCreatePLDictionary(NULL, NULL); - - while (1) { - c = getNonSpaceChar(pldata); - if (c == 0) { - COMPLAIN(pldata, _("unterminated PropList dictionary")); - ok = 0; - break; - } else if (c == '}') { - break; - } - - DPUT("getting PropList dictionary key"); - if (c == '<') { - key = getPLData(pldata); - } else if (c == '"') { - key = getPLQString(pldata); - } else if (ISSTRINGABLE(c)) { - pldata->pos--; - key = getPLString(pldata); - } else { - if (c == '=') { - COMPLAIN(pldata, _("missing PropList dictionary key")); - } else { - COMPLAIN(pldata, _("missing PropList dictionary entry key " - "or unterminated dictionary")); - } - ok = 0; - break; - } - - if (!key) { - COMPLAIN(pldata, _("error parsing PropList dictionary key")); - ok = 0; - break; - } - - c = getNonSpaceChar(pldata); - if (c != '=') { - WMReleasePropList(key); - COMPLAIN(pldata, _("missing = in PropList dictionary entry")); - ok = 0; - break; - } - - DPUT("getting PropList dictionary entry value for key"); - value = getPropList(pldata); - if (!value) { - COMPLAIN(pldata, _("error parsing PropList dictionary entry value")); - WMReleasePropList(key); - ok = 0; - break; - } - - c = getNonSpaceChar(pldata); - if (c != ';') { - COMPLAIN(pldata, _("missing ; in PropList dictionary entry")); - WMReleasePropList(key); - WMReleasePropList(value); - ok = 0; - break; - } - - WMPutInPLDictionary(dict, key, value); - WMReleasePropList(key); - WMReleasePropList(value); - } - - if (!ok) { - WMReleasePropList(dict); - dict = NULL; - } - - return dict; -} - -static WMPropList *getPropList(PLData * pldata) -{ - WMPropList *plist; - int c; - - c = getNonSpaceChar(pldata); - - switch (c) { - case 0: - DPUT("End of PropList"); - plist = NULL; - break; - - case '{': - DPUT("Getting PropList dictionary"); - plist = getPLDictionary(pldata); - break; - - case '(': - DPUT("Getting PropList array"); - plist = getPLArray(pldata); - break; - - case '<': - DPUT("Getting PropList data"); - plist = getPLData(pldata); - break; - - case '"': - DPUT("Getting PropList quoted string"); - plist = getPLQString(pldata); - break; - - default: - if (ISSTRINGABLE(c)) { - DPUT("Getting PropList string"); - pldata->pos--; - plist = getPLString(pldata); - } else { - COMPLAIN(pldata, _("was expecting a string, data, array or " - "dictionary. If it's a string, try enclosing " "it with \".")); - if (c == '#' || c == '/') { - wwarning(_("Comments are not allowed inside WindowMaker owned" " domain files.")); - } - plist = NULL; - } - break; - } - - return plist; -} - -void WMPLSetCaseSensitive(Bool caseSensitiveness) -{ - caseSensitive = caseSensitiveness; -} - -WMPropList *WMCreatePLString(const char *str) -{ - WMPropList *plist; - - wassertrv(str != NULL, NULL); - - plist = (WMPropList *) wmalloc(sizeof(W_PropList)); - plist->type = WPLString; - plist->d.string = wstrdup(str); - plist->retainCount = 1; - - return plist; -} - -WMPropList *WMCreatePLData(WMData * data) -{ - WMPropList *plist; - - wassertrv(data != NULL, NULL); - - plist = (WMPropList *) wmalloc(sizeof(W_PropList)); - plist->type = WPLData; - plist->d.data = WMRetainData(data); - plist->retainCount = 1; - - return plist; -} - -WMPropList *WMCreatePLDataWithBytes(const unsigned char *bytes, unsigned int length) -{ - WMPropList *plist; - - wassertrv(bytes != NULL, NULL); - - plist = (WMPropList *) wmalloc(sizeof(W_PropList)); - plist->type = WPLData; - plist->d.data = WMCreateDataWithBytes(bytes, length); - plist->retainCount = 1; - - return plist; -} - -WMPropList *WMCreatePLDataWithBytesNoCopy(unsigned char *bytes, unsigned int length, WMFreeDataProc * destructor) -{ - WMPropList *plist; - - wassertrv(bytes != NULL, NULL); - - plist = (WMPropList *) wmalloc(sizeof(W_PropList)); - plist->type = WPLData; - plist->d.data = WMCreateDataWithBytesNoCopy(bytes, length, destructor); - plist->retainCount = 1; - - return plist; -} +/* + * This should be written in Rust whenever va_args support improves. + */ WMPropList *WMCreatePLArray(WMPropList * elem, ...) { WMPropList *plist, *nelem; va_list ap; - plist = (WMPropList *) wmalloc(sizeof(W_PropList)); - plist->type = WPLArray; - plist->d.array = WMCreateArray(4); - plist->retainCount = 1; + plist = WMCreateEmptyPLArray(); if (!elem) return plist; - WMAddToArray(plist->d.array, WMRetainPropList(elem)); + WMAddToPLArray(plist, elem); va_start(ap, elem); @@ -979,888 +25,6 @@ WMPropList *WMCreatePLArray(WMPropList * elem, ...) va_end(ap); return plist; } - WMAddToArray(plist->d.array, WMRetainPropList(nelem)); + WMAddToPLArray(plist, elem); } } - -WMPropList *WMCreatePLDictionary(WMPropList * key, WMPropList * value, ...) -{ - WMPropList *plist, *nkey, *nvalue, *k, *v; - va_list ap; - - plist = (WMPropList *) wmalloc(sizeof(W_PropList)); - plist->type = WPLDictionary; - plist->d.dict = WMCreateHashTable(WMPropListHashCallbacks); - plist->retainCount = 1; - - if (!key || !value) - return plist; - - WMHashInsert(plist->d.dict, WMRetainPropList(key), WMRetainPropList(value)); - - va_start(ap, value); - - while (1) { - nkey = va_arg(ap, WMPropList *); - if (!nkey) { - va_end(ap); - return plist; - } - nvalue = va_arg(ap, WMPropList *); - if (!nvalue) { - va_end(ap); - return plist; - } - if (WMHashGetItemAndKey(plist->d.dict, nkey, (void **)&v, (void **)&k)) { - WMHashRemove(plist->d.dict, k); - WMReleasePropList(k); - WMReleasePropList(v); - } - WMHashInsert(plist->d.dict, WMRetainPropList(nkey), WMRetainPropList(nvalue)); - } -} - -WMPropList *WMRetainPropList(WMPropList * plist) -{ - WMPropList *key, *value; - WMHashEnumerator e; - int i; - - plist->retainCount++; - - switch (plist->type) { - case WPLString: - case WPLData: - break; - case WPLArray: - for (i = 0; i < WMGetArrayItemCount(plist->d.array); i++) { - WMRetainPropList(WMGetFromArray(plist->d.array, i)); - } - break; - case WPLDictionary: - e = WMEnumerateHashTable(plist->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&value, (void **)&key)) { - WMRetainPropList(key); - WMRetainPropList(value); - } - break; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertrv(False, NULL); - break; - } - - return plist; -} - -void WMReleasePropList(WMPropList * plist) -{ - WMPropList *key, *value; - WMHashEnumerator e; - int i; - - plist->retainCount--; - - switch (plist->type) { - case WPLString: - if (plist->retainCount < 1) { - wfree(plist->d.string); - wfree(plist); - } - break; - case WPLData: - if (plist->retainCount < 1) { - WMReleaseData(plist->d.data); - wfree(plist); - } - break; - case WPLArray: - for (i = 0; i < WMGetArrayItemCount(plist->d.array); i++) { - WMReleasePropList(WMGetFromArray(plist->d.array, i)); - } - if (plist->retainCount < 1) { - WMFreeArray(plist->d.array); - wfree(plist); - } - break; - case WPLDictionary: - e = WMEnumerateHashTable(plist->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&value, (void **)&key)) { - WMReleasePropList(key); - WMReleasePropList(value); - } - if (plist->retainCount < 1) { - WMFreeHashTable(plist->d.dict); - wfree(plist); - } - break; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertr(False); - break; - } -} - -void WMInsertInPLArray(WMPropList * plist, int index, WMPropList * item) -{ - wassertr(plist->type == WPLArray); - - retainPropListByCount(item, plist->retainCount); - WMInsertInArray(plist->d.array, index, item); -} - -void WMAddToPLArray(WMPropList * plist, WMPropList * item) -{ - wassertr(plist->type == WPLArray); - - retainPropListByCount(item, plist->retainCount); - WMAddToArray(plist->d.array, item); -} - -void WMDeleteFromPLArray(WMPropList * plist, int index) -{ - WMPropList *item; - - wassertr(plist->type == WPLArray); - - item = WMGetFromArray(plist->d.array, index); - if (item != NULL) { - WMDeleteFromArray(plist->d.array, index); - releasePropListByCount(item, plist->retainCount); - } -} - -void WMRemoveFromPLArray(WMPropList * plist, WMPropList * item) -{ - WMPropList *iPtr; - int i; - - wassertr(plist->type == WPLArray); - - for (i = 0; i < WMGetArrayItemCount(plist->d.array); i++) { - iPtr = WMGetFromArray(plist->d.array, i); - if (WMIsPropListEqualTo(item, iPtr)) { - WMDeleteFromArray(plist->d.array, i); - releasePropListByCount(iPtr, plist->retainCount); - break; - } - } -} - -void WMPutInPLDictionary(WMPropList * plist, WMPropList * key, WMPropList * value) -{ - wassertr(plist->type == WPLDictionary); - - /*WMRetainPropList(key); */ - WMRemoveFromPLDictionary(plist, key); - retainPropListByCount(key, plist->retainCount); - retainPropListByCount(value, plist->retainCount); - WMHashInsert(plist->d.dict, key, value); - /*WMReleasePropList(key); */ -} - -void WMRemoveFromPLDictionary(WMPropList * plist, WMPropList * key) -{ - WMPropList *k, *v; - - wassertr(plist->type == WPLDictionary); - - if (WMHashGetItemAndKey(plist->d.dict, key, (void **)&v, (void **)&k)) { - WMHashRemove(plist->d.dict, k); - releasePropListByCount(k, plist->retainCount); - releasePropListByCount(v, plist->retainCount); - } -} - -WMPropList *WMMergePLDictionaries(WMPropList * dest, WMPropList * source, Bool recursive) -{ - WMPropList *key, *value, *dvalue; - WMHashEnumerator e; - - wassertrv(source->type == WPLDictionary && dest->type == WPLDictionary, NULL); - - if (source == dest) - return dest; - - e = WMEnumerateHashTable(source->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&value, (void **)&key)) { - if (recursive && value->type == WPLDictionary) { - dvalue = WMHashGet(dest->d.dict, key); - if (dvalue && dvalue->type == WPLDictionary) { - WMMergePLDictionaries(dvalue, value, True); - } else { - WMPutInPLDictionary(dest, key, value); - } - } else { - WMPutInPLDictionary(dest, key, value); - } - } - - return dest; -} - -WMPropList *WMSubtractPLDictionaries(WMPropList * dest, WMPropList * source, Bool recursive) -{ - WMPropList *key, *value, *dvalue; - WMHashEnumerator e; - - wassertrv(source->type == WPLDictionary && dest->type == WPLDictionary, NULL); - - if (source == dest) { - WMPropList *keys = WMGetPLDictionaryKeys(dest); - int i; - - for (i = 0; i < WMGetArrayItemCount(keys->d.array); i++) { - WMRemoveFromPLDictionary(dest, WMGetFromArray(keys->d.array, i)); - } - WMReleasePropList(keys); - return dest; - } - - e = WMEnumerateHashTable(source->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&value, (void **)&key)) { - dvalue = WMHashGet(dest->d.dict, key); - if (!dvalue) - continue; - if (WMIsPropListEqualTo(value, dvalue)) { - WMRemoveFromPLDictionary(dest, key); - } else if (recursive && value->type == WPLDictionary && dvalue->type == WPLDictionary) { - WMSubtractPLDictionaries(dvalue, value, True); - } - } - - return dest; -} - -int WMGetPropListItemCount(WMPropList * plist) -{ - switch (plist->type) { - case WPLString: - case WPLData: - return 0; /* should this be 1 instead? */ - case WPLArray: - return WMGetArrayItemCount(plist->d.array); - case WPLDictionary: - return (int)WMCountHashTable(plist->d.dict); - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertrv(False, 0); - break; - } - - return 0; -} - -Bool WMIsPLString(WMPropList * plist) -{ - if (plist) - return (plist->type == WPLString); - else - return False; -} - -Bool WMIsPLData(WMPropList * plist) -{ - if (plist) - return (plist->type == WPLData); - else - return False; -} - -Bool WMIsPLArray(WMPropList * plist) -{ - if (plist) - return (plist->type == WPLArray); - else - return False; -} - -Bool WMIsPLDictionary(WMPropList * plist) -{ - if (plist) - return (plist->type == WPLDictionary); - else - return False; -} - -Bool WMIsPropListEqualTo(WMPropList * plist, WMPropList * other) -{ - WMPropList *key1, *item1, *item2; - WMHashEnumerator enumerator; - int n, i; - - if (plist->type != other->type) - return False; - - switch (plist->type) { - case WPLString: - if (caseSensitive) { - return (strcmp(plist->d.string, other->d.string) == 0); - } else { - return (strcasecmp(plist->d.string, other->d.string) == 0); - } - case WPLData: - return WMIsDataEqualToData(plist->d.data, other->d.data); - case WPLArray: - n = WMGetArrayItemCount(plist->d.array); - if (n != WMGetArrayItemCount(other->d.array)) - return False; - for (i = 0; i < n; i++) { - item1 = WMGetFromArray(plist->d.array, i); - item2 = WMGetFromArray(other->d.array, i); - if (!WMIsPropListEqualTo(item1, item2)) - return False; - } - return True; - case WPLDictionary: - if (WMCountHashTable(plist->d.dict) != WMCountHashTable(other->d.dict)) - return False; - enumerator = WMEnumerateHashTable(plist->d.dict); - while (WMNextHashEnumeratorItemAndKey(&enumerator, (void **)&item1, (void **)&key1)) { - item2 = WMHashGet(other->d.dict, key1); - if (!item2 || !item1 || !WMIsPropListEqualTo(item1, item2)) - return False; - } - return True; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertrv(False, False); - break; - } - - return False; -} - -char *WMGetFromPLString(WMPropList * plist) -{ - wassertrv(plist->type == WPLString, NULL); - - return plist->d.string; -} - -WMData *WMGetFromPLData(WMPropList * plist) -{ - wassertrv(plist->type == WPLData, NULL); - - return plist->d.data; -} - -const unsigned char *WMGetPLDataBytes(WMPropList * plist) -{ - wassertrv(plist->type == WPLData, NULL); - - return WMDataBytes(plist->d.data); -} - -int WMGetPLDataLength(WMPropList * plist) -{ - wassertrv(plist->type == WPLData, 0); - - return WMGetDataLength(plist->d.data); -} - -WMPropList *WMGetFromPLArray(WMPropList * plist, int index) -{ - wassertrv(plist->type == WPLArray, NULL); - - return WMGetFromArray(plist->d.array, index); -} - -WMPropList *WMGetFromPLDictionary(WMPropList * plist, WMPropList * key) -{ - wassertrv(plist->type == WPLDictionary, NULL); - - return WMHashGet(plist->d.dict, key); -} - -WMPropList *WMGetPLDictionaryKeys(WMPropList * plist) -{ - WMPropList *array, *key; - WMHashEnumerator enumerator; - - wassertrv(plist->type == WPLDictionary, NULL); - - array = (WMPropList *) wmalloc(sizeof(W_PropList)); - array->type = WPLArray; - array->d.array = WMCreateArray(WMCountHashTable(plist->d.dict)); - array->retainCount = 1; - - enumerator = WMEnumerateHashTable(plist->d.dict); - while ((key = WMNextHashEnumeratorKey(&enumerator))) { - WMAddToArray(array->d.array, WMRetainPropList(key)); - } - - return array; -} - -WMPropList *WMShallowCopyPropList(WMPropList * plist) -{ - WMPropList *ret = NULL; - WMPropList *key, *item; - WMHashEnumerator e; - WMData *data; - int i; - - switch (plist->type) { - case WPLString: - ret = WMCreatePLString(plist->d.string); - break; - case WPLData: - data = WMCreateDataWithData(plist->d.data); - ret = WMCreatePLData(data); - WMReleaseData(data); - break; - case WPLArray: - ret = (WMPropList *) wmalloc(sizeof(W_PropList)); - ret->type = WPLArray; - ret->d.array = WMCreateArrayWithArray(plist->d.array); - ret->retainCount = 1; - - for (i = 0; i < WMGetArrayItemCount(ret->d.array); i++) - WMRetainPropList(WMGetFromArray(ret->d.array, i)); - - break; - case WPLDictionary: - ret = WMCreatePLDictionary(NULL, NULL); - e = WMEnumerateHashTable(plist->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&item, (void **)&key)) { - WMPutInPLDictionary(ret, key, item); - } - break; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertrv(False, NULL); - break; - } - - return ret; -} - -WMPropList *WMDeepCopyPropList(WMPropList * plist) -{ - WMPropList *ret = NULL; - WMPropList *key, *item; - WMHashEnumerator e; - WMData *data; - int i; - - switch (plist->type) { - case WPLString: - ret = WMCreatePLString(plist->d.string); - break; - case WPLData: - data = WMCreateDataWithData(plist->d.data); - ret = WMCreatePLData(data); - WMReleaseData(data); - break; - case WPLArray: - ret = WMCreatePLArray(NULL); - for (i = 0; i < WMGetArrayItemCount(plist->d.array); i++) { - item = WMDeepCopyPropList(WMGetFromArray(plist->d.array, i)); - WMAddToArray(ret->d.array, item); - } - break; - case WPLDictionary: - ret = WMCreatePLDictionary(NULL, NULL); - e = WMEnumerateHashTable(plist->d.dict); - /* While we copy an existing dictionary there is no way that we can - * have duplicate keys, so we don't need to first remove a key/value - * pair before inserting the new key/value. - */ - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&item, (void **)&key)) { - WMHashInsert(ret->d.dict, WMDeepCopyPropList(key), WMDeepCopyPropList(item)); - } - break; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertrv(False, NULL); - break; - } - - return ret; -} - -WMPropList *WMCreatePropListFromDescription(const char *desc) -{ - WMPropList *plist = NULL; - PLData *pldata; - - pldata = (PLData *) wmalloc(sizeof(PLData)); - pldata->ptr = desc; - pldata->lineNumber = 1; - - plist = getPropList(pldata); - - if (getNonSpaceChar(pldata) != 0 && plist) { - COMPLAIN(pldata, _("extra data after end of property list")); - /* - * We can't just ignore garbage after the end of the description - * (especially if the description was read from a file), because - * the "garbage" can be the real data and the real garbage is in - * fact in the beginning of the file (which is now inside plist) - */ - WMReleasePropList(plist); - plist = NULL; - } - - wfree(pldata); - - return plist; -} - -char *WMGetPropListDescription(WMPropList * plist, Bool indented) -{ - return (indented ? indentedDescription(plist, 0) : description(plist)); -} - -WMPropList *WMReadPropListFromFile(const char *file) -{ - WMPropList *plist = NULL; - PLData *pldata; - char *read_buf; - FILE *f; - struct stat stbuf; - size_t length; - - f = fopen(file, "rb"); - if (!f) { - /* let the user print the error message if he really needs to */ - /*werror(_("could not open domain file '%s' for reading"), file); */ - return NULL; - } - - if (stat(file, &stbuf) == 0) { - length = (size_t) stbuf.st_size; - } else { - werror(_("could not get size for file '%s'"), file); - fclose(f); - return NULL; - } - - read_buf = wmalloc(length + 1); - if (fread(read_buf, length, 1, f) != 1) { - if (ferror(f)) { - werror(_("error reading from file '%s'"), file); - } - fclose(f); - wfree(read_buf); - return NULL; - } - read_buf[length] = '\0'; - fclose(f); - - pldata = (PLData *) wmalloc(sizeof(PLData)); - pldata->ptr = read_buf; - pldata->filename = file; - pldata->lineNumber = 1; - - plist = getPropList(pldata); - - if (getNonSpaceChar(pldata) != 0 && plist) { - COMPLAIN(pldata, _("extra data after end of property list")); - /* - * We can't just ignore garbage after the end of the description - * (especially if the description was read from a file), because - * the "garbage" can be the real data and the real garbage is in - * fact in the beginning of the file (which is now inside plist) - */ - WMReleasePropList(plist); - plist = NULL; - } - - wfree(read_buf); - wfree(pldata); - - return plist; -} - -WMPropList *WMReadPropListFromPipe(const char *command) -{ - FILE *file; - WMPropList *plist; - PLData *pldata; - char *read_buf, *read_ptr; - size_t remain_size, line_size; - const size_t block_read_size = 4096; - const size_t block_read_margin = 512; - - file = popen(command, "r"); - - if (!file) { - werror(_("%s:could not open menu file"), command); - return NULL; - } - - /* read from file till EOF or OOM and fill proplist buffer*/ - remain_size = block_read_size; - read_buf = wmalloc(remain_size); - read_ptr = read_buf; - while (fgets(read_ptr, remain_size, file) != NULL) { - line_size = strlen(read_ptr); - - remain_size -= line_size; - read_ptr += line_size; - - if (remain_size < block_read_margin) { - size_t read_length; - - read_length = read_ptr - read_buf; - read_buf = wrealloc(read_buf, read_length + block_read_size); - read_ptr = read_buf + read_length; - remain_size = block_read_size; - } - } - - pclose(file); - - pldata = (PLData *) wmalloc(sizeof(PLData)); - pldata->ptr = read_buf; - pldata->filename = command; - pldata->lineNumber = 1; - - plist = getPropList(pldata); - - if (getNonSpaceChar(pldata) != 0 && plist) { - COMPLAIN(pldata, _("extra data after end of property list")); - /* - * We can't just ignore garbage after the end of the description - * (especially if the description was read from a file), because - * the "garbage" can be the real data and the real garbage is in - * fact in the beginning of the file (which is now inside plist) - */ - WMReleasePropList(plist); - plist = NULL; - } - - wfree(read_buf); - wfree(pldata); - - return plist; -} - -/* TODO: review this function's code */ - -Bool WMWritePropListToFile(WMPropList * plist, const char *path) -{ - char *thePath = NULL; - char *desc; - FILE *theFile; -#ifdef HAVE_MKSTEMP - int fd, mask; -#endif - - if (!wmkdirhier(path)) - return False; - - /* Use the path name of the destination file as a prefix for the - * mkstemp() call so that we can be sure that both files are on - * the same filesystem and the subsequent rename() will work. */ - thePath = wstrconcat(path, ".XXXXXX"); - -#ifdef HAVE_MKSTEMP - /* - * We really just want to read the current umask, but as Coverity is - * pointing a possible security issue: - * some versions of mkstemp do not set file rights properly on the - * created file, so it is recommended so set the umask beforehand. - * As we need to set an umask to read the current value, we take this - * opportunity to set a temporary aggresive umask so Coverity won't - * complain, even if we do not really care in the present use case. - */ - mask = umask(S_IRWXG | S_IRWXO); - if ((fd = mkstemp(thePath)) < 0) { - werror(_("mkstemp (%s) failed"), thePath); - goto failure; - } - umask(mask); - fchmod(fd, 0666 & ~mask); - if ((theFile = fdopen(fd, "wb")) == NULL) { - close(fd); - } -#else - if (mktemp(thePath) == NULL) { - werror(_("mktemp (%s) failed"), thePath); - goto failure; - } - theFile = fopen(thePath, "wb"); -#endif - - if (theFile == NULL) { - werror(_("open (%s) failed"), thePath); - goto failure; - } - - desc = indentedDescription(plist, 0); - - if (fprintf(theFile, "%s\n", desc) != strlen(desc) + 1) { - werror(_("writing to file: %s failed"), thePath); - wfree(desc); - fclose(theFile); - goto failure; - } - - wfree(desc); - - (void)fsync(fileno(theFile)); - if (fclose(theFile) != 0) { - werror(_("fclose (%s) failed"), thePath); - goto failure; - } - - /* If we used a temporary file, we still need to rename() it be the - * real file. Also, we need to try to retain the file attributes of - * the original file we are overwriting (if we are) */ - if (rename(thePath, path) != 0) { - werror(_("rename ('%s' to '%s') failed"), thePath, path); - goto failure; - } - - wfree(thePath); - return True; - - failure: - unlink(thePath); - wfree(thePath); - return False; -} - -/* - * create a directory hierarchy - * - * if the last octet of `path' is `/', the full path is - * assumed to be a directory; otherwise path is assumed to be a - * file, and the last component is stripped off. the rest is the - * the hierarchy to be created. - * - * refuses to create anything outside $WMAKER_USER_ROOT - * - * returns 1 on success, 0 on failure - */ -int wmkdirhier(const char *path) -{ - const char *t; - char *thePath = NULL, buf[1024]; - size_t p, plen; - struct stat st; - - /* Only create directories under $WMAKER_USER_ROOT */ - if ((t = wusergnusteppath()) == NULL) - return 0; - if (strncmp(path, t, strlen(t)) != 0) - return 0; - - thePath = wstrdup(path); - /* Strip the trailing component if it is a file */ - p = strlen(thePath); - while (p && thePath[p] != '/') - thePath[p--] = '\0'; - - thePath[p] = '\0'; - - /* Shortcut if it already exists */ - if (stat(thePath, &st) == 0) { - wfree(thePath); - if (S_ISDIR(st.st_mode)) { - /* Is a directory alright */ - return 1; - } else { - /* Exists, but not a directory, the caller - * might just as well abort now */ - return 0; - } - } - - memset(buf, 0, sizeof(buf)); - strncpy(buf, t, sizeof(buf) - 1); - p = strlen(buf); - plen = strlen(thePath); - - do { - while (p++ < plen && thePath[p] != '/') - ; - - strncpy(buf, thePath, p); - if (mkdir(buf, 0777) == -1 && errno == EEXIST && - stat(buf, &st) == 0 && !S_ISDIR(st.st_mode)) { - werror(_("Could not create component %s"), buf); - wfree(thePath); - return 0; - } - } while (p < plen); - - wfree(thePath); - return 1; -} - -/* ARGSUSED2 */ -static int wrmdirhier_fn(const char *path, const struct stat *st, - int type, struct FTW *ftw) -{ - /* Parameter not used, but tell the compiler that it is ok */ - (void) st; - (void) ftw; - - switch(type) { - case FTW_D: - break; - case FTW_DP: - return rmdir(path); - break; - case FTW_F: - case FTW_SL: - case FTW_SLN: - return unlink(path); - break; - case FTW_DNR: - case FTW_NS: - default: - return EPERM; - } - - /* NOTREACHED */ - return 0; -} - -/* - * remove a directory hierarchy - * - * refuses to remove anything outside $WMAKER_USER_ROOT/Defaults or $WMAKER_USER_ROOT/Library - * - * returns 1 on success, 0 on failure - * - * TODO: revisit what's error and what's not - * - * with inspirations from OpenBSD's bin/rm/rm.c - */ -int wrmdirhier(const char *path) -{ - const char *libpath; - char *udefpath = NULL; - struct stat st; - int error; - - /* Only remove directories under $WMAKER_USER_ROOT/Defaults or $WMAKER_USER_ROOT/Library */ - libpath = wuserdatapath(); - if (strncmp(path, libpath, strlen(libpath)) == 0) - if (path[strlen(libpath)] == '/') - goto path_in_valid_tree; - - udefpath = wdefaultspathfordomain(""); - if (strncmp(path, udefpath, strlen(udefpath)) == 0) - /* Note: by side effect, 'udefpath' already contains a final '/' */ - goto path_in_valid_tree; - - wfree(udefpath); - return EPERM; - - path_in_valid_tree: - wfree(udefpath); - - /* Shortcut if it doesn't exist to begin with */ - if (stat(path, &st) == -1) - return ENOENT; - - error = nftw(path, wrmdirhier_fn, 1, FTW_PHYS); - - return error; -} diff --git a/WINGs/selection.c b/WINGs/selection.c index 5d5221b8..63d364b0 100644 --- a/WINGs/selection.c +++ b/WINGs/selection.c @@ -237,7 +237,7 @@ static void handleRequestEvent(XEvent * event) } /* delete handlers */ - copy = WMDuplicateArray(selHandlers); + copy = WMCreateArrayWithArray(selHandlers); WM_ITERATE_ARRAY(copy, handler, iter) { if (handler && handler->flags.delete_pending) { WMDeleteSelectionHandler(handler->view, handler->selection, handler->timestamp); @@ -261,8 +261,9 @@ static WMData *getSelectionData(Display * dpy, Window win, Atom where) bpi = bits / 8; - wdata = WMCreateDataWithBytesNoCopy(data, len * bpi, (void *) XFree); + wdata = WMCreateDataWithBytes(data, len * bpi); WMSetDataFormat(wdata, bits); + XFree(data); return wdata; } @@ -300,7 +301,7 @@ static void handleNotifyEvent(XEvent * event) } /* delete callbacks */ - copy = WMDuplicateArray(selCallbacks); + copy = WMCreateArrayWithArray(selCallbacks); WM_ITERATE_ARRAY(copy, handler, iter) { if (handler && handler->flags.delete_pending) { WMDeleteSelectionCallback(handler->view, handler->selection, handler->timestamp); diff --git a/WINGs/userdefaults.c b/WINGs/userdefaults.c index bc01723c..40010c55 100644 --- a/WINGs/userdefaults.c +++ b/WINGs/userdefaults.c @@ -46,40 +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; - const 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(); - if (!h) - return NULL; - - pathlen = strlen(h); - path = wmalloc(pathlen + sizeof(subdir)); - strcpy(path, h); - strcpy(path + pathlen, subdir); - - return path; -} - const char *wuserdatapath(void) { static char *path = NULL; @@ -331,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]) { @@ -404,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]) { diff --git a/WINGs/wballoon.c b/WINGs/wballoon.c index c87b6c9e..8a9c8a84 100644 --- a/WINGs/wballoon.c +++ b/WINGs/wballoon.c @@ -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; diff --git a/WINGs/wcolorpanel.c b/WINGs/wcolorpanel.c index b799e0d0..35443d81 100644 --- a/WINGs/wcolorpanel.c +++ b/WINGs/wcolorpanel.c @@ -2994,10 +2994,13 @@ static void customPaletteMenuNewFromFile(W_ColorPanel * panel) int i; RImage *tmpImg = NULL; - if ((!panel->lastBrowseDir) || (strcmp(panel->lastBrowseDir, "\0") == 0)) - spath = wexpandpath(wgethomedir()); - else + if ((!panel->lastBrowseDir) || (strcmp(panel->lastBrowseDir, "\0") == 0)) { + char *homedir = wgethomedir(); + spath = wexpandpath(homedir); + wfree(homedir); + } else { spath = wexpandpath(panel->lastBrowseDir); + } browseP = WMGetOpenPanel(scr); WMSetFilePanelCanChooseDirectories(browseP, 0); diff --git a/WINGs/wfilepanel.c b/WINGs/wfilepanel.c index c1cb16ac..1da05355 100644 --- a/WINGs/wfilepanel.c +++ b/WINGs/wfilepanel.c @@ -761,17 +761,15 @@ static void goFloppy(WMWidget *widget, void *p_panel) static void goHome(WMWidget *widget, void *p_panel) { WMFilePanel *panel = p_panel; - const char *home; + char *home; /* Parameter not used, but tell the compiler that it is ok */ (void) widget; - /* home is statically allocated. Don't free it! */ home = wgethomedir(); - if (!home) - return; WMSetFilePanelDirectory(panel, home); + wfree(home); } static void handleEvents(XEvent * event, void *data) diff --git a/WINGs/wfont.c b/WINGs/wfont.c index 999aaa44..52af4bfc 100644 --- a/WINGs/wfont.c +++ b/WINGs/wfont.c @@ -51,12 +51,15 @@ static char *xlfdToFcName(const char *xlfd) { FcPattern *pattern; char *fname; + char *result; pattern = xlfdToFcPattern(xlfd); fname = (char *)FcNameUnparse(pattern); + result = wstrdup(fname); + free(fname); FcPatternDestroy(pattern); - return fname; + return result; } static Bool hasProperty(FcPattern * pattern, const char *property) @@ -92,6 +95,7 @@ static Bool hasPropertyWithStringValue(FcPattern * pattern, const char *object, static char *makeFontOfSize(const char *font, int size, const char *fallback) { FcPattern *pattern; + char *name; char *result; if (font[0] == '-') { @@ -115,7 +119,9 @@ static char *makeFontOfSize(const char *font, int size, const char *fallback) /*FcPatternPrint(pattern); */ - result = (char *)FcNameUnparse(pattern); + name = (char *)FcNameUnparse(pattern); + result = wstrdup(name); + free(name); FcPatternDestroy(pattern); return result; @@ -421,7 +427,7 @@ WMFont *WMCopyFontWithStyle(WMScreen * scrPtr, WMFont * font, WMFontStyle style) name = (char *)FcNameUnparse(pattern); copy = WMCreateFont(scrPtr, name); FcPatternDestroy(pattern); - wfree(name); + free(name); return copy; } diff --git a/WINGs/wfontpanel.c b/WINGs/wfontpanel.c index df01ed65..c57678ab 100644 --- a/WINGs/wfontpanel.c +++ b/WINGs/wfontpanel.c @@ -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++) { diff --git a/WINGs/widgets.c b/WINGs/widgets.c index 95e4fd77..e0434f5f 100644 --- a/WINGs/widgets.c +++ b/WINGs/widgets.c @@ -610,10 +610,9 @@ WMScreen *WMCreateScreenWithRContext(Display * display, int screen, RContext * c assert(W_ApplicationInitialized()); } - scrPtr = malloc(sizeof(W_Screen)); + scrPtr = wmalloc(sizeof(W_Screen)); if (!scrPtr) return NULL; - memset(scrPtr, 0, sizeof(W_Screen)); scrPtr->aflags.hasAppIcon = 1; @@ -630,7 +629,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); diff --git a/WPrefs.app/Appearance.c b/WPrefs.app/Appearance.c index 01ddc28e..028c96e0 100644 --- a/WPrefs.app/Appearance.c +++ b/WPrefs.app/Appearance.c @@ -1110,7 +1110,7 @@ static void deleteTexture(WMWidget * w, void *data) static void extractTexture(WMWidget * w, void *data) { _Panel *panel = (_Panel *) data; - char *path; + char *path, *homedir; WMOpenPanel *opanel; WMScreen *scr = WMWidgetScreen(w); @@ -1118,13 +1118,17 @@ static void extractTexture(WMWidget * w, void *data) WMSetFilePanelCanChooseDirectories(opanel, False); WMSetFilePanelCanChooseFiles(opanel, True); - if (WMRunModalFilePanelForDirectory(opanel, panel->parent, wgethomedir(), _("Select File"), NULL)) { + homedir = wgethomedir(); + if (WMRunModalFilePanelForDirectory(opanel, panel->parent, homedir, _("Select File"), NULL)) { path = WMGetFilePanelFileName(opanel); OpenExtractPanelFor(panel); wfree(path); } + if (homedir) { + wfree(homedir); + } } static void changePage(WMWidget * w, void *data) @@ -2224,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++) { @@ -2246,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; diff --git a/WPrefs.app/Docks.c b/WPrefs.app/Docks.c index c83332c8..3a796dca 100644 --- a/WPrefs.app/Docks.c +++ b/WPrefs.app/Docks.c @@ -129,7 +129,7 @@ static void autoDelayChanged(void *observerData, WMNotification *notification) } char *value = WMGetTextFieldText(anAutoDelayT); adjustButtonSelectionBasedOnValue(panel, row, value); - free(value); + wfree(value); return; } } diff --git a/WPrefs.app/Focus.c b/WPrefs.app/Focus.c index 04198751..476a0898 100644 --- a/WPrefs.app/Focus.c +++ b/WPrefs.app/Focus.c @@ -138,7 +138,7 @@ static void storeData(_Panel * panel) if (sscanf(str, "%i", &i) != 1) i = 0; SetIntegerForKey(i, "RaiseDelay"); - free(str); + wfree(str); SetBoolForKey(WMGetButtonSelected(panel->ignB), "IgnoreFocusClick"); SetBoolForKey(WMGetButtonSelected(panel->newB), "AutoFocus"); diff --git a/WPrefs.app/FontSimple.c b/WPrefs.app/FontSimple.c index 2147b985..13f01edb 100644 --- a/WPrefs.app/FontSimple.c +++ b/WPrefs.app/FontSimple.c @@ -288,6 +288,7 @@ static char *getSelectedFont(_Panel * panel, FcChar8 * curfont) WMListItem *item; FcPattern *pat; char *name; + char *result; if (curfont) pat = FcNameParse(curfont); @@ -321,9 +322,12 @@ static char *getSelectedFont(_Panel * panel, FcChar8 * curfont) } name = (char *)FcNameUnparse(pat); + result = wstrdup(name); + free(name); FcPatternDestroy(pat); - return name; + + return result; } static void updateSampleFont(_Panel * panel) diff --git a/WPrefs.app/HotCornerShortcuts.c b/WPrefs.app/HotCornerShortcuts.c index d3259d80..73595cdc 100644 --- a/WPrefs.app/HotCornerShortcuts.c +++ b/WPrefs.app/HotCornerShortcuts.c @@ -147,11 +147,11 @@ static void storeData(_Panel * panel) if (sscanf(str, "%i", &i) != 1) i = 0; SetIntegerForKey(i, "HotCornerDelay"); - free(str); + wfree(str); 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) diff --git a/WPrefs.app/Makefile.am b/WPrefs.app/Makefile.am index 4408ff96..50c258eb 100644 --- a/WPrefs.app/Makefile.am +++ b/WPrefs.app/Makefile.am @@ -66,6 +66,7 @@ AM_CPPFLAGS = -DRESOURCE_PATH=\"$(wpdatadir)\" -DWMAKER_RESOURCE_PATH=\"$(pkgdat WPrefs_DEPENDENCIES = $(top_builddir)/WINGs/libWINGs.la WPrefs_LDADD = \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a\ $(top_builddir)/WINGs/libWINGs.la\ $(top_builddir)/WINGs/libWUtil.la\ $(top_builddir)/wrlib/libwraster.la \ diff --git a/WPrefs.app/Paths.c b/WPrefs.app/Paths.c index f48eb484..d9fe4978 100644 --- a/WPrefs.app/Paths.c +++ b/WPrefs.app/Paths.c @@ -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); diff --git a/WPrefs.app/TexturePanel.c b/WPrefs.app/TexturePanel.c index f5863a3b..3272013f 100644 --- a/WPrefs.app/TexturePanel.c +++ b/WPrefs.app/TexturePanel.c @@ -627,7 +627,7 @@ static void browseImageCallback(WMWidget *w, void *data) WMSetFilePanelCanChooseFiles(opanel, True); if (!ipath) - ipath = wstrdup(wgethomedir()); + ipath = wgethomedir(); if (WMRunModalFilePanelForDirectory(opanel, panel->win, ipath, _("Open Image"), NULL)) { char *path, *fullpath; diff --git a/WPrefs.app/WPrefs.c b/WPrefs.app/WPrefs.c index cbffa5af..c95f8a17 100644 --- a/WPrefs.app/WPrefs.c +++ b/WPrefs.app/WPrefs.c @@ -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; diff --git a/WPrefs.app/main.c b/WPrefs.app/main.c index 2a76a203..1312115c 100644 --- a/WPrefs.app/main.c +++ b/WPrefs.app/main.c @@ -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); @@ -87,8 +79,6 @@ int main(int argc, char **argv) int i; char *display_name = ""; - wsetabort(wAbort); - memset(DeadHandlers, 0, sizeof(DeadHandlers)); WMInitializeApplication("WPrefs", &argc, argv); @@ -155,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); diff --git a/configure.ac b/configure.ac index f00c21a6..78313976 100644 --- a/configure.ac +++ b/configure.ac @@ -54,10 +54,12 @@ AC_CHECK_PROG(CARGO, [cargo], [yes], [no]) AS_IF(test x$CARGO = xno, AC_MSG_ERROR([cargo is required. Please set the CARGO environment variable or install the Rust toolchain from https://www.rust-lang.org/]) ) +AC_SUBST(CARGO, [cargo]) AC_CHECK_PROG(RUSTC, [rustc], [yes], [no]) AS_IF(test x$RUSTC = xno, AC_MSG_ERROR([rustc is required. Please set the RUSTC environment variable or install the Rust toolchain from https://www.rust-lang.org/]) ) +AC_SUBST(RUSTC, [rustc]) dnl libtool library versioning dnl ========================== @@ -351,24 +353,6 @@ AS_IF([test "x$enable_mwm_hints" = "xno"], AM_CONDITIONAL([USE_MWM_HINTS], [test "x$enable_mwm_hints" != "xno"]) -dnl Boehm GC -dnl ======== -m4_divert_push([INIT_PREPARE])dnl -AC_ARG_ENABLE([boehm-gc], - [AS_HELP_STRING([--enable-boehm-gc], [use Boehm GC instead of the default libc malloc() [default=no]])], - [AS_CASE(["$enableval"], - [yes], [with_boehm_gc=yes], - [no], [with_boehm_gc=no], - [AC_MSG_ERROR([bad value $enableval for --enable-boehm-gc])] )], - [with_boehm_gc=no]) -m4_divert_pop([INIT_PREPARE])dnl - -AS_IF([test "x$with_boehm_gc" = "xyes"], - AC_SEARCH_LIBS([GC_malloc], [gc], - [AC_DEFINE(USE_BOEHM_GC, 1, [Define if Boehm GC is to be used])], - [AC_MSG_FAILURE([--enable-boehm-gc specified but test for libgc failed])])) - - dnl LCOV dnl ==== m4_divert_push([INIT_PREPARE])dnl @@ -973,6 +957,9 @@ AC_CONFIG_FILES( wrlib/Makefile wrlib/po/Makefile wrlib/tests/Makefile + dnl Rust implementation of WINGs libraries + wutil-rs/Makefile + dnl WINGs toolkit WINGs/Makefile WINGs/WINGs/Makefile WINGs/po/Makefile WINGs/Documentation/Makefile WINGs/Resources/Makefile WINGs/Extras/Makefile diff --git a/doc/build/Compilation.texi b/doc/build/Compilation.texi index 2265e4fd..9908bd61 100644 --- a/doc/build/Compilation.texi +++ b/doc/build/Compilation.texi @@ -253,14 +253,6 @@ If found, then the library @emph{WRaster} can use the @emph{ImageMagick} library @sc{Window Maker} support more image formats, like @emph{SVG}, @emph{BMP}, @emph{TGA}, ... You can get it from @uref{http://www.imagemagick.org/} -@item @emph{Boehm GC} - -This library can be used by the @emph{WINGs} utility toolkit to use a -@cite{Boehm-Demers-Weiser Garbage Collector} instead of the traditional -@command{malloc}/@command{free} functions from the @emph{libc}. -You have to explicitly ask for its support though (@pxref{Configure Options}). -You can get it from @uref{http://www.hboehm.info/gc/} - @end itemize @@ -468,8 +460,6 @@ You can find more information about the libraries in the @ref{Optional Dependencies}. @table @option -@item --enable-boehm-gc -Never enabled by default, use Boehm GC instead of the default @emph{libc} @command{malloc()} @item --disable-gif Disable GIF support in @emph{WRaster} library; when enabled use @file{libgif} or @file{libungif}. diff --git a/src/Makefile.am b/src/Makefile.am index f704516a..8ba6f2af 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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@ \ diff --git a/src/appicon.c b/src/appicon.c index 9e94b961..4160696f 100644 --- a/src/appicon.c +++ b/src/appicon.c @@ -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; diff --git a/src/appmenu.c b/src/appmenu.c index bc708964..11191641 100644 --- a/src/appmenu.c +++ b/src/appmenu.c @@ -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) diff --git a/src/client.c b/src/client.c index e452f2df..7d64d78b 100644 --- a/src/client.c +++ b/src/client.c @@ -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); } diff --git a/src/defaults.c b/src/defaults.c index 26961e60..1532692a 100644 --- a/src/defaults.c +++ b/src/defaults.c @@ -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]; diff --git a/src/dialog.c b/src/dialog.c index 391dda23..e1aad51a 100644 --- a/src/dialog.c +++ b/src/dialog.c @@ -39,10 +39,6 @@ #include #include -#ifdef HAVE_MALLOC_H -#include -#endif - #include #ifdef __FreeBSD__ #include @@ -250,7 +246,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))); @@ -367,7 +363,7 @@ static void handleHistoryKeyPress(XEvent * event, void *clientData) case XK_Up: if (p->histpos < WMGetArrayItemCount(p->history) - 1) { if (p->histpos == 0) - wfree(WMReplaceInArray(p->history, 0, WMGetTextFieldText(p->panel->text))); + wfree(WMSetInArray(p->history, 0, WMGetTextFieldText(p->panel->text))); p->histpos++; WMSetTextFieldText(p->panel->text, WMGetFromArray(p->history, p->histpos)); } @@ -468,7 +464,7 @@ int wAdvancedInputDialog(WScreen *scr, const char *title, const char *message, c if (p->panel->result == WAPRDefault) { result = WMGetTextFieldText(p->panel->text); - wfree(WMReplaceInArray(p->history, 0, wstrdup(result))); + wfree(WMSetInArray(p->history, 0, wstrdup(result))); SaveHistory(p->history, filename); } else result = NULL; @@ -1361,7 +1357,7 @@ void wShowInfoPanel(WScreen *scr) char *posn = getPrettyOSName(); if (posn) { snprintf(buffer, sizeof(buffer), _("Running on: %s (%s)\n"), posn, uts.machine); - free(posn); + wfree(posn); } else snprintf(buffer, sizeof(buffer), _("Running on: %s (%s)\n"), uts.sysname, uts.machine); @@ -1393,31 +1389,6 @@ void wShowInfoPanel(WScreen *scr) break; } -#if defined(HAVE_MALLOC_H) && defined(HAVE_MALLINFO2) - { - struct mallinfo2 ma = mallinfo2(); - snprintf(buffer, sizeof(buffer), -#ifdef DEBUG - _("Total memory allocated: %lu kB (in use: %lu kB, %lu free chunks)\n"), -#else - _("Total memory allocated: %lu kB (in use: %lu kB)\n"), -#endif - (ma.arena + ma.hblkhd) / 1024, - (ma.uordblks + ma.hblkhd) / 1024 -#ifdef DEBUG - /* - * This information is representative of the memory - * fragmentation. In ideal case it should be 1, but - * that is never possible - */ - , ma.ordblks -#endif - ); - - strbuf = wstrappend(strbuf, buffer); - } -#endif - strbuf = wstrappend(strbuf, _("Image formats: ")); strl = RSupportedFileFormats(); separator = NULL; diff --git a/src/dock.c b/src/dock.c index d59cf35c..19ed862f 100644 --- a/src/dock.c +++ b/src/dock.c @@ -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); @@ -3194,7 +3196,7 @@ static pid_t execCommand(WAppIcon *btn, const char *command, WSavedState *state) setsid(); #endif - args = malloc(sizeof(char *) * (argc + 1)); + args = wmalloc(sizeof(char *) * (argc + 1)); if (!args) exit(111); @@ -3338,8 +3340,8 @@ void wDockTrackWindowLaunch(WDock *dock, Window window) char *command = NULL; if (!PropGetWMClass(window, &wm_class, &wm_instance)) { - free(wm_class); - free(wm_instance); + wfree(wm_class); + wfree(wm_instance); return; } @@ -3419,10 +3421,8 @@ void wDockTrackWindowLaunch(WDock *dock, Window window) if (command) wfree(command); - if (wm_class) - free(wm_class); - if (wm_instance) - free(wm_instance); + wfree(wm_class); + wfree(wm_instance); } void wClipUpdateForWorkspaceChange(WScreen *scr, int workspace) @@ -5072,7 +5072,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 +5114,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) { diff --git a/src/event.c b/src/event.c index 7356a1a2..b8e64c28 100644 --- a/src/event.c +++ b/src/event.c @@ -141,7 +141,7 @@ WMagicNumber wAddDeathHandler(pid_t pid, WDeathHandler * callback, void *cdata) { DeathHandler *handler; - handler = malloc(sizeof(DeathHandler)); + handler = wmalloc(sizeof(DeathHandler)); if (!handler) return 0; @@ -150,7 +150,7 @@ WMagicNumber wAddDeathHandler(pid_t pid, WDeathHandler * callback, void *cdata) handler->client_data = cdata; if (!deathHandlers) - deathHandlers = WMCreateArrayWithDestructor(8, free); + deathHandlers = WMCreateArrayWithDestructor(8, wfree); WMAddToArray(deathHandlers, handler); @@ -164,7 +164,7 @@ static void wdelete_death_handler(WMagicNumber id) if (!handler || !deathHandlers) return; - /* array destructor will call free(handler) */ + /* array destructor will call wfree(handler) */ WMRemoveFromArray(deathHandlers, handler); } @@ -1796,7 +1796,7 @@ static void handleKeyPress(XEvent * event) } if (wwin->flags.selected && scr->selected_windows) { - scr->shortcutWindows[widx] = WMDuplicateArray(scr->selected_windows); + scr->shortcutWindows[widx] = WMCreateArrayWithArray(scr->selected_windows); /*WMRemoveFromArray(scr->shortcutWindows[index], wwin); WMInsertInArray(scr->shortcutWindows[index], 0, wwin); */ } else { @@ -1816,7 +1816,7 @@ static void handleKeyPress(XEvent * event) if (scr->shortcutWindows[widx]) { WMFreeArray(scr->shortcutWindows[widx]); } - scr->shortcutWindows[widx] = WMDuplicateArray(scr->selected_windows); + scr->shortcutWindows[widx] = WMCreateArrayWithArray(scr->selected_windows); } } diff --git a/src/geomview.c b/src/geomview.c index 3564ffac..64ec5835 100644 --- a/src/geomview.c +++ b/src/geomview.c @@ -40,7 +40,7 @@ WGeometryView *WCreateGeometryView(WMScreen * scr) widgetClass = W_RegisterUserWidget(); } - gview = malloc(sizeof(WGeometryView)); + gview = wmalloc(sizeof(WGeometryView)); if (!gview) { return NULL; } diff --git a/src/main.c b/src/main.c index 0f2f9e00..757db9f5 100644 --- a/src/main.c +++ b/src/main.c @@ -131,7 +131,7 @@ static void setWVisualID(int screen, int val) /* no array at all, alloc space for screen + 1 entries * and init with default value */ wVisualID_len = screen + 1; - wVisualID = (int *)malloc(wVisualID_len * sizeof(int)); + wVisualID = (int *)wmalloc(wVisualID_len * sizeof(int)); for (i = 0; i < wVisualID_len; i++) { wVisualID[i] = -1; } @@ -156,7 +156,7 @@ static void setWVisualID(int screen, int val) */ static int initWVisualID(const char *user_str) { - char *mystr = strdup(user_str); + char *mystr = wstrdup(user_str); int cur_in_pos = 0; int cur_out_pos = 0; int cur_screen = 0; @@ -191,7 +191,7 @@ static int initWVisualID(const char *user_str) cur_in_pos++; } - free(mystr); + wfree(mystr); if (cur_screen == 0||error_found != 0) return 1; @@ -383,7 +383,7 @@ Bool RelaunchWindow(WWindow *wwin) setsid(); #endif /* argv is not null-terminated */ - char **a = (char **) malloc(argc + 1); + char **a = (char **) wmalloc(argc + 1); if (! a) { werror("out of memory trying to relaunch the application"); Exit(-1); @@ -624,7 +624,6 @@ static int real_main(int argc, char **argv) int d, s; setlocale(LC_ALL, ""); - wsetabort(wAbort); /* for telling WPrefs what's the name of the wmaker binary being ran */ setenv("WMAKER_BIN_NAME", argv[0], 1); diff --git a/src/menu.c b/src/menu.c index e84b1caa..2a83633b 100644 --- a/src/menu.c +++ b/src/menu.c @@ -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"); diff --git a/src/misc.c b/src/misc.c index 4cab3584..123def2b 100644 --- a/src/misc.c +++ b/src/misc.c @@ -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; diff --git a/src/properties.c b/src/properties.c index 6751306c..b42b1646 100644 --- a/src/properties.c +++ b/src/properties.c @@ -54,13 +54,13 @@ int PropGetWMClass(Window window, char **wm_class, char **wm_instance) class_hint = XAllocClassHint(); if (XGetClassHint(dpy, window, class_hint) == 0) { - *wm_class = strdup("default"); - *wm_instance = strdup("default"); + *wm_class = wstrdup("default"); + *wm_instance = wstrdup("default"); XFree(class_hint); return False; } - *wm_instance = strdup(class_hint->res_name); - *wm_class = strdup(class_hint->res_class); + *wm_instance = wstrdup(class_hint->res_name); + *wm_class = wstrdup(class_hint->res_class); XFree(class_hint->res_name); XFree(class_hint->res_class); @@ -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; diff --git a/src/rootmenu.c b/src/rootmenu.c index 213c30e8..c7931580 100644 --- a/src/rootmenu.c +++ b/src/rootmenu.c @@ -1243,7 +1243,7 @@ static WMenu *readMenuDirectory(WScreen *scr, const char *title, char **path, co if (dentry->d_name[0] == '.') continue; - buffer = malloc(strlen(path[i]) + strlen(dentry->d_name) + 4); + buffer = wmalloc(strlen(path[i]) + strlen(dentry->d_name) + 4); if (!buffer) { werror(_("out of memory while constructing directory menu %s"), path[i]); break; @@ -1288,7 +1288,7 @@ static WMenu *readMenuDirectory(WScreen *scr, const char *title, char **path, co } } } - free(buffer); + wfree(buffer); } closedir(dir); @@ -1315,7 +1315,7 @@ static WMenu *readMenuDirectory(WScreen *scr, const char *title, char **path, co length += 7; if (command) length += strlen(command) + 6; - buffer = malloc(length); + buffer = wmalloc(length); if (!buffer) { werror(_("out of memory while constructing directory menu %s"), path[data->index]); break; @@ -1353,7 +1353,7 @@ static WMenu *readMenuDirectory(WScreen *scr, const char *title, char **path, co if (command) length += strlen(command); - buffer = malloc(length); + buffer = wmalloc(length); if (!buffer) { werror(_("out of memory while constructing directory menu %s"), path[data->index]); break; diff --git a/src/screen.c b/src/screen.c index 98e9f949..6bb7832c 100644 --- a/src/screen.c +++ b/src/screen.c @@ -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); diff --git a/src/session.c b/src/session.c index c01eba2f..bd6bcfbf 100644 --- a/src/session.c +++ b/src/session.c @@ -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); @@ -289,9 +290,9 @@ static WMPropList *makeWindowState(WWindow * wwin, WApplication * wapp) } if (instance) - free(instance); + wfree(instance); if (class) - free(class); + wfree(class); if (command) wfree(command); @@ -313,7 +314,7 @@ void wSessionSaveState(WScreen * scr) return; } - list = WMCreatePLArray(NULL); + list = WMCreateEmptyPLArray(); wapp_list = WMCreateArray(16); @@ -382,7 +383,7 @@ static pid_t execCommand(WScreen *scr, char *command) SetupEnvironment(scr); - args = malloc(sizeof(char *) * (argc + 1)); + args = wmalloc(sizeof(char *) * (argc + 1)); if (!args) exit(111); for (i = 0; i < argc; i++) { @@ -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); diff --git a/src/superfluous.c b/src/superfluous.c index c0e40be9..65d4d843 100644 --- a/src/superfluous.c +++ b/src/superfluous.c @@ -244,7 +244,7 @@ reinit: wApplicationSetBouncing(data->wapp, 0); WMDeleteTimerHandler(data->timer); wApplicationDestroy(data->wapp); - free(data); + wfree(data); } static int bounceDirection(WAppIcon *aicon) @@ -324,7 +324,7 @@ void wAppBounce(WApplication *wapp) wApplicationIncrementRefcount(wapp); wApplicationSetBouncing(wapp, 1); - AppBouncerData *data = (AppBouncerData *)malloc(sizeof(AppBouncerData)); + AppBouncerData *data = (AppBouncerData *)wmalloc(sizeof(AppBouncerData)); data->wapp = wapp; data->count = data->pow = 0; data->dir = bounceDirection(wApplicationGetAppIcon(wapp)); diff --git a/src/switchpanel.c b/src/switchpanel.c index cb6cda10..9597779e 100644 --- a/src/switchpanel.c +++ b/src/switchpanel.c @@ -132,7 +132,7 @@ static void changeImage(WSwitchPanel *panel, int idecks, int selected, Bool dim, if (flags == desired && !force) return; - WMReplaceInArray(panel->flags, idecks, (void *) (uintptr_t) desired); + WMSetInArray(panel->flags, idecks, (void *) (uintptr_t) desired); if (!panel->bg && !panel->tile && !selected) WMSetFrameRelief(icon, WRFlat); @@ -357,8 +357,7 @@ static void drawTitle(WSwitchPanel *panel, int idecks, const char *title) WMSetLabelText(panel->label, ntitle); } - if (ntitle) - free(ntitle); + wfree(ntitle); } static WMArray *makeWindowListArray(WScreen *scr, int include_unmapped, Bool class_only) diff --git a/src/wdefaults.c b/src/wdefaults.c index 7aad3db5..e2fe5009 100644 --- a/src/wdefaults.c +++ b/src/wdefaults.c @@ -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 ----------------------- */ diff --git a/src/window.c b/src/window.c index 9e0622df..6c2a01be 100644 --- a/src/window.c +++ b/src/window.c @@ -283,10 +283,10 @@ void wWindowDestroy(WWindow *wwin) XFree(wwin->wm_hints); if (wwin->wm_instance) - XFree(wwin->wm_instance); + wfree(wwin->wm_instance); if (wwin->wm_class) - XFree(wwin->wm_class); + wfree(wwin->wm_class); if (wwin->wm_gnustep_attr) wfree(wwin->wm_gnustep_attr); @@ -950,10 +950,10 @@ WWindow *wManageWindow(WScreen *scr, Window window) } if (instance) - free(instance); + wfree(instance); if (class) - free(class); + wfree(class); #undef ADEQUATE } @@ -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); @@ -2602,7 +2602,7 @@ void wWindowSetShape(WWindow * wwin) if (!rects) goto alt_code; - urec = malloc(sizeof(XRectangle) * (count + 2)); + urec = wmalloc(sizeof(XRectangle) * (count + 2)); if (!urec) { XFree(rects); goto alt_code; @@ -2779,7 +2779,7 @@ WMagicNumber wWindowAddSavedState(const char *instance, const char *class, { WWindowState *wstate; - wstate = malloc(sizeof(WWindowState)); + wstate = wmalloc(sizeof(WWindowState)); if (!wstate) return NULL; @@ -2841,9 +2841,9 @@ WMagicNumber wWindowGetSavedState(Window win) if (command) wfree(command); if (instance) - free(instance); + wfree(instance); if (class) - free(class); + wfree(class); return wstate; } diff --git a/src/winmenu.c b/src/winmenu.c index 422e0663..3680aa79 100644 --- a/src/winmenu.c +++ b/src/winmenu.c @@ -346,7 +346,7 @@ static void makeShortcutCommand(WMenu * menu, WMenuEntry * entry) } if (wwin->flags.selected && scr->selected_windows) { - scr->shortcutWindows[index] = WMDuplicateArray(scr->selected_windows); + scr->shortcutWindows[index] = WMCreateArrayWithArray(scr->selected_windows); /*WMRemoveFromArray(scr->shortcutWindows[index], wwin); WMInsertInArray(scr->shortcutWindows[index], 0, wwin); */ } else { diff --git a/src/winspector.c b/src/winspector.c index eacfdf95..0af1a789 100644 --- a/src/winspector.c +++ b/src/winspector.c @@ -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) diff --git a/src/workspace.c b/src/workspace.c index dad14eb5..6686c1a1 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -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); diff --git a/src/xdnd.c b/src/xdnd.c index 54c0df55..42cf4ee9 100644 --- a/src/xdnd.c +++ b/src/xdnd.c @@ -256,7 +256,7 @@ static void wXDNDGetTypeList(Display *dpy, Window window) return; } - typelist = malloc((count + 1) * sizeof(Atom)); + typelist = wmalloc((count + 1) * sizeof(Atom)); a = (Atom *) data; for (i = 0; i < count; i++) { typelist[i] = a[i]; @@ -267,7 +267,7 @@ static void wXDNDGetTypeList(Display *dpy, Window window) } typelist[count] = 0; XFree(data); - free(typelist); + wfree(typelist); } Bool wXDNDProcessClientMessage(XClientMessageEvent *event) diff --git a/start-captive-wmaker.sh b/start-captive-wmaker.sh index 32a5f402..2afb4c33 100755 --- a/start-captive-wmaker.sh +++ b/start-captive-wmaker.sh @@ -77,10 +77,18 @@ if [ -n "$1" -a -x "$WindowMaker$1" ] ; then shift fi -Xephyr -screen 1080x760 :1 & +for i in $(seq 5 10) ; do + if [ "x$DISPLAY" != "x:$i" ] ; then + xephyr_display=":$i" + break + fi +done +echo "Running Xephyr on display $xephyr_display" + +Xephyr -screen 640x480 "$xephyr_display" & xephyr_pid=$! -DISPLAY=:1 gdb \ +DISPLAY="$xephyr_display" gdb \ --directory "$project_base" \ --quiet \ - --args "$WindowMaker" -display :1 --for-real "$@" + --args "$WindowMaker" -display "$xephyr_display" --for-real "$@" kill $xephyr_pid diff --git a/util/Makefile.am b/util/Makefile.am index 0c148702..dd026050 100644 --- a/util/Makefile.am +++ b/util/Makefile.am @@ -18,31 +18,51 @@ AM_CPPFLAGS = \ liblist= @LIBRARY_SEARCH_PATH@ @INTLIBS@ -wdwrite_LDADD = $(top_builddir)/WINGs/libWUtil.la $(liblist) +wdwrite_LDADD = \ + $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ + $(liblist) -wdread_LDADD = $(top_builddir)/WINGs/libWUtil.la $(liblist) +wdread_LDADD = \ + $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ + $(liblist) -wxcopy_LDADD = @XLFLAGS@ @XLIBS@ +wxcopy_LDADD = @XLFLAGS@ @XLIBS@ \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a wxpaste_LDADD = @XLFLAGS@ @XLIBS@ -getstyle_LDADD = $(top_builddir)/WINGs/libWUtil.la $(liblist) +getstyle_LDADD = \ + $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ + $(liblist) getstyle_SOURCES = getstyle.c fontconv.c common.h setstyle_LDADD = \ $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ @XLFLAGS@ @XLIBS@ $(liblist) setstyle_SOURCES = setstyle.c fontconv.c common.h -convertfonts_LDADD = $(top_builddir)/WINGs/libWUtil.la $(liblist) +convertfonts_LDADD = \ + $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ + $(liblist) convertfonts_SOURCES = convertfonts.c fontconv.c common.h -seticons_LDADD= $(top_builddir)/WINGs/libWUtil.la $(liblist) +seticons_LDADD= \ + $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ + $(liblist) -geticonset_LDADD= $(top_builddir)/WINGs/libWUtil.la $(liblist) +geticonset_LDADD= \ + $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ + $(liblist) wmagnify_LDADD = \ $(top_builddir)/WINGs/libWINGs.la \ @@ -52,18 +72,21 @@ wmagnify_LDADD = \ wmsetbg_LDADD = \ $(top_builddir)/WINGs/libWINGs.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ $(top_builddir)/WINGs/libWUtil.la \ $(top_builddir)/wrlib/libwraster.la \ @XLFLAGS@ @LIBXINERAMA@ @XLIBS@ @INTLIBS@ wmgenmenu_LDADD = \ $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ @INTLIBS@ wmgenmenu_SOURCES = wmgenmenu.c wmgenmenu.h wmmenugen_LDADD = \ $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ @INTLIBS@ wmmenugen_SOURCES = wmmenugen.c wmmenugen.h wmmenugen_misc.c \ @@ -75,6 +98,8 @@ wmiv_CFLAGS = @PANGO_CFLAGS@ @PTHREAD_CFLAGS@ wmiv_LDADD = \ $(top_builddir)/wrlib/libwraster.la \ $(top_builddir)/WINGs/libWINGs.la \ + $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ @XLFLAGS@ @XLIBS@ @GFXLIBS@ \ @PANGO_LIBS@ @PTHREAD_LIBS@ @LIBEXIF@ diff --git a/util/convertfonts.c b/util/convertfonts.c index 1674ced8..d653e724 100644 --- a/util/convertfonts.c +++ b/util/convertfonts.c @@ -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) { diff --git a/util/geticonset.c b/util/geticonset.c index 4505551c..8941bb11 100644 --- a/util/geticonset.c +++ b/util/geticonset.c @@ -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); } } diff --git a/util/getstyle.c b/util/getstyle.c index a2935892..a3d5f5fd 100644 --- a/util/getstyle.c +++ b/util/getstyle.c @@ -242,7 +242,7 @@ static void makeThemePack(WMPropList * style, const char *themeName) WMDeleteFromPLArray(value, 1); WMInsertInPLArray(value, 1, WMCreatePLString(newPath)); - free(newPath); + wfree(newPath); } else { findCopyFile(themeDir, WMGetFromPLString(file)); } @@ -262,7 +262,7 @@ static void makeThemePack(WMPropList * style, const char *themeName) WMDeleteFromPLArray(value, 1); WMInsertInPLArray(value, 1, WMCreatePLString(newPath)); - free(newPath); + wfree(newPath); } else { findCopyFile(themeDir, WMGetFromPLString(file)); } @@ -277,7 +277,7 @@ static void makeThemePack(WMPropList * style, const char *themeName) WMDeleteFromPLArray(value, 2); WMInsertInPLArray(value, 2, WMCreatePLString(newPath)); - free(newPath); + wfree(newPath); } else { findCopyFile(themeDir, WMGetFromPLString(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]); diff --git a/util/setstyle.c b/util/setstyle.c index 0883459a..4fcdfcc2 100644 --- a/util/setstyle.c +++ b/util/setstyle.c @@ -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"); @@ -465,7 +470,7 @@ int main(int argc, char **argv) } buf[strlen(buf) - 6 /* strlen("/style") */] = '\0'; - homedir = wstrdup(wgethomedir()); + homedir = wgethomedir(); if (strlen(homedir) > 1 && /* this is insane, wgethomedir() returns `/' on error */ strncmp(homedir, buf, strlen(homedir)) == 0) { /* theme pack is under ${HOME}; exchange ${HOME} part diff --git a/util/wdwrite.c b/util/wdwrite.c index 2ff16706..c169ad64 100644 --- a/util/wdwrite.c +++ b/util/wdwrite.c @@ -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); } diff --git a/util/wmiv.c b/util/wmiv.c index 0d33a535..925d3afa 100755 --- a/util/wmiv.c +++ b/util/wmiv.c @@ -202,7 +202,7 @@ int change_title(XTextProperty *prop, char *filename) XSetWMName(dpy, win, prop); if (prop->value) XFree(prop->value); - free(combined_title); + wfree(combined_title); return EXIT_SUCCESS; } @@ -596,8 +596,8 @@ int linked_list_add(linked_list_t *list, const void *data) { link_t *link; - /* calloc sets the "next" field to zero. */ - link = calloc(1, sizeof(link_t)); + /* wmalloc zeros the buffer it returns, so the "next" is zero. */ + link = wmalloc(sizeof(link_t)); if (!link) { fprintf(stderr, "Error: memory allocation failed\n"); return EXIT_FAILURE; @@ -627,8 +627,8 @@ void linked_list_free(linked_list_t *list) /* Store the next value so that we don't access freed memory. */ next = link->next; if (link->data) - free((char *)link->data); - free(link); + wfree((char *)link->data); + wfree(link); } } @@ -651,7 +651,7 @@ link_t *connect_dir(char *dirpath, linked_list_t *li) /* maybe it's a file */ struct stat stDirInfo; if (lstat(dirpath, &stDirInfo) == 0) { - linked_list_add(li, strdup(dirpath)); + linked_list_add(li, wstrdup(dirpath)); return li->first; } else { return NULL; @@ -664,11 +664,11 @@ link_t *connect_dir(char *dirpath, linked_list_t *li) else snprintf(path, PATH_MAX, "%s%c%s", dirpath, FILE_SEPARATOR, dir[idx]->d_name); - free(dir[idx]); + wfree(dir[idx]); if ((lstat(path, &stDirInfo) == 0) && !S_ISDIR(stDirInfo.st_mode)) - linked_list_add(li, strdup(path)); + linked_list_add(li, wstrdup(path)); } - free(dir); + wfree(dir); return li->first; } diff --git a/util/wmmenugen_misc.c b/util/wmmenugen_misc.c index 161a32e0..56f2c254 100644 --- a/util/wmmenugen_misc.c +++ b/util/wmmenugen_misc.c @@ -121,7 +121,7 @@ void parse_locale(const char *what, char **language, char **country, char **enco *language = wstrdup(e); out: - free(e); + wfree(e); return; } diff --git a/util/wmsetbg.c b/util/wmsetbg.c index dc747d04..652e64da 100644 --- a/util/wmsetbg.c +++ b/util/wmsetbg.c @@ -398,7 +398,7 @@ static BackgroundTexture *parseTexture(RContext * rc, char *text) RGradientStyle gtype; int iwidth, iheight; - colors = malloc(sizeof(RColor *) * (count - 1)); + colors = wmalloc(sizeof(RColor *) * (count - 1)); if (!colors) { wwarning("out of memory while parsing texture"); goto error; @@ -425,7 +425,7 @@ static BackgroundTexture *parseTexture(RContext * rc, char *text) wfree(colors); goto error; } - if (!(colors[i - 2] = malloc(sizeof(RColor)))) { + if (!(colors[i - 2] = wmalloc(sizeof(RColor)))) { wwarning("out of memory while parsing texture"); for (j = 0; colors[j] != NULL; j++) @@ -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); diff --git a/util/wxcopy.c b/util/wxcopy.c index 82b4c7f2..6fd76592 100644 --- a/util/wxcopy.c +++ b/util/wxcopy.c @@ -26,6 +26,8 @@ #include #include +#include + #include "../src/wconfig.h" #define LINESIZE (4*1024) @@ -197,7 +199,7 @@ int main(int argc, char **argv) break; } if (buf_len == 0) { - nbuf = malloc(buf_len = l + nl + 1); + nbuf = wmalloc(buf_len = l + nl + 1); } else if (buf_len < l + nl + 1) { /* * To avoid terrible performance on big input buffers, @@ -205,12 +207,7 @@ int main(int argc, char **argv) * current line. */ buf_len = 2 * buf_len + nl + 1; - /* some realloc implementations don't do malloc if buf==NULL */ - if (buf == NULL) { - nbuf = malloc(buf_len); - } else { - nbuf = realloc(buf, buf_len); - } + nbuf = wrealloc(buf, buf_len); } else { nbuf = buf; } diff --git a/wmlib/Makefile.am b/wmlib/Makefile.am index 8b87fdf9..33cd075c 100644 --- a/wmlib/Makefile.am +++ b/wmlib/Makefile.am @@ -6,7 +6,8 @@ lib_LTLIBRARIES = libWMaker.la include_HEADERS = WMaker.h -AM_CPPFLAGS = $(DFLAGS) @XCFLAGS@ +AM_CPPFLAGS = $(DFLAGS) @XCFLAGS@ \ + -I$(top_srcdir)/WINGs -I$(top_builddir)/WINGs libWMaker_la_LIBADD = @XLFLAGS@ @XLIBS@ diff --git a/wmlib/app.c b/wmlib/app.c index 9cd27c8c..9a3fdf3d 100644 --- a/wmlib/app.c +++ b/wmlib/app.c @@ -24,6 +24,8 @@ #include #include +#include + #include "WMaker.h" #include "app.h" @@ -31,7 +33,7 @@ WMAppContext *WMAppCreateWithMain(Display * display, int screen_number, Window m { wmAppContext *ctx; - ctx = malloc(sizeof(wmAppContext)); + ctx = wmalloc(sizeof(wmAppContext)); if (!ctx) return NULL; @@ -39,9 +41,9 @@ WMAppContext *WMAppCreateWithMain(Display * display, int screen_number, Window m ctx->screen_number = screen_number; ctx->our_leader_hint = False; ctx->main_window = main_window; - ctx->windows = malloc(sizeof(Window)); + ctx->windows = wmalloc(sizeof(Window)); if (!ctx->windows) { - free(ctx); + wfree(ctx); return NULL; } ctx->win_count = 1; @@ -58,13 +60,13 @@ int WMAppAddWindow(WMAppContext * app, Window window) { Window *win; - win = malloc(sizeof(Window) * (app->win_count + 1)); + win = wmalloc(sizeof(Window) * (app->win_count + 1)); if (!win) return False; memcpy(win, app->windows, sizeof(Window) * app->win_count); - free(app->windows); + wfree(app->windows); win[app->win_count] = window; app->windows = win; diff --git a/wmlib/menu.c b/wmlib/menu.c index 6646cd70..a3a1498d 100644 --- a/wmlib/menu.c +++ b/wmlib/menu.c @@ -26,6 +26,8 @@ #include #include +#include + #include "WMaker.h" #include "app.h" #include "menu.h" @@ -37,7 +39,7 @@ WMMenu *WMMenuCreate(WMAppContext * app, char *title) if (strlen(title) > 255) return NULL; - menu = malloc(sizeof(wmMenu)); + menu = wmalloc(sizeof(wmMenu)); if (!menu) return NULL; @@ -50,12 +52,12 @@ WMMenu *WMMenuCreate(WMAppContext * app, char *title) menu->realized = False; menu->code = app->last_menu_tag++; - menu->entryline = malloc(strlen(title) + 32); - menu->entryline2 = malloc(32); + menu->entryline = wmalloc(strlen(title) + 32); + menu->entryline2 = wmalloc(32); if (!menu->entryline || !menu->entryline2) { if (menu->entryline) - free(menu->entryline); - free(menu); + wfree(menu->entryline); + wfree(menu); return NULL; } sprintf(menu->entryline, "%i %i %s", wmBeginMenu, menu->code, title); @@ -77,13 +79,13 @@ WMMenuAddItem(WMMenu * menu, char *text, WMMenuAction action, if (strlen(text) > 255) return -1; - entry = malloc(sizeof(wmMenuEntry)); + entry = wmalloc(sizeof(wmMenuEntry)); if (!entry) return -1; - entry->entryline = malloc(strlen(text) + 100); + entry->entryline = wmalloc(strlen(text) + 100); if (!entry->entryline) { - free(entry); + wfree(entry); return -1; } @@ -125,13 +127,13 @@ int WMMenuAddSubmenu(WMMenu * menu, char *text, WMMenu * submenu) if (strlen(text) > 255) return -1; - entry = malloc(sizeof(wmMenuEntry)); + entry = wmalloc(sizeof(wmMenuEntry)); if (!entry) return -1; - entry->entryline = malloc(strlen(text) + 100); + entry->entryline = wmalloc(strlen(text) + 100); if (!entry->entryline) { - free(entry); + wfree(entry); return -1; } @@ -217,7 +219,7 @@ int WMRealizeMenus(WMAppContext * app) return True; count++; - slist = malloc(count * sizeof(char *)); + slist = wmalloc(count * sizeof(char *)); if (!slist) { return False; } @@ -227,10 +229,10 @@ int WMRealizeMenus(WMAppContext * app) addItems(slist, &i, app->main_menu); if (!XStringListToTextProperty(slist, i, &text_prop)) { - free(slist); + wfree(slist); return False; } - free(slist); + wfree(slist); XSetTextProperty(app->dpy, app->main_window, &text_prop, getatom(app->dpy)); XFree(text_prop.value); diff --git a/wrlib/Makefile.am b/wrlib/Makefile.am index a82bb9a0..5f88852a 100644 --- a/wrlib/Makefile.am +++ b/wrlib/Makefile.am @@ -82,7 +82,8 @@ libwraster_la_SOURCES += load_magick.c endif AM_CFLAGS = @MAGICKFLAGS@ -AM_CPPFLAGS = $(DFLAGS) @HEADER_SEARCH_PATH@ +AM_CPPFLAGS = $(DFLAGS) @HEADER_SEARCH_PATH@ \ + -I$(top_srcdir)/WINGs -I$(top_builddir)/WINGs libwraster_la_LIBADD = @LIBRARY_SEARCH_PATH@ @GFXLIBS@ @MAGICKLIBS@ @XLIBS@ @LIBXMU@ -lm diff --git a/wrlib/context.c b/wrlib/context.c index 73044506..0a993605 100644 --- a/wrlib/context.c +++ b/wrlib/context.c @@ -37,6 +37,8 @@ #include +#include + #include "wraster.h" #include "scale.h" #include "wr_i18n.h" @@ -104,17 +106,17 @@ static Bool allocateStandardPseudoColor(RContext * ctx, XStandardColormap * stdc return False; } - ctx->colors = malloc(sizeof(XColor) * ctx->ncolors); + ctx->colors = wmalloc(sizeof(XColor) * ctx->ncolors); if (!ctx->colors) { RErrorCode = RERR_NOMEMORY; return False; } - ctx->pixels = malloc(sizeof(unsigned long) * ctx->ncolors); + ctx->pixels = wmalloc(sizeof(unsigned long) * ctx->ncolors); if (!ctx->pixels) { - free(ctx->colors); + wfree(ctx->colors); ctx->colors = NULL; RErrorCode = RERR_NOMEMORY; @@ -246,15 +248,15 @@ static Bool allocatePseudoColor(RContext *ctx) assert(cpc >= 2 && ncolors <= (1 << ctx->depth)); - colors = malloc(sizeof(XColor) * ncolors); + colors = wmalloc(sizeof(XColor) * ncolors); if (!colors) { RErrorCode = RERR_NOMEMORY; return False; } - ctx->pixels = malloc(sizeof(unsigned long) * ncolors); + ctx->pixels = wmalloc(sizeof(unsigned long) * ncolors); if (!ctx->pixels) { - free(colors); + wfree(colors); RErrorCode = RERR_NOMEMORY; return False; } @@ -343,7 +345,7 @@ static XColor *allocateGrayScale(RContext * ctx) ctx->attribs->render_mode = RBestMatchRendering; } - colors = malloc(sizeof(XColor) * ncolors); + colors = wmalloc(sizeof(XColor) * ncolors); if (!colors) { RErrorCode = RERR_NOMEMORY; return False; @@ -526,7 +528,7 @@ RContext *RCreateContext(Display * dpy, int screen_number, const RContextAttribu RContext *context; XGCValues gcv; - context = malloc(sizeof(RContext)); + context = wmalloc(sizeof(RContext)); if (!context) { RErrorCode = RERR_NOMEMORY; return NULL; @@ -537,9 +539,9 @@ RContext *RCreateContext(Display * dpy, int screen_number, const RContextAttribu context->screen_number = screen_number; - context->attribs = malloc(sizeof(RContextAttributes)); + context->attribs = wmalloc(sizeof(RContextAttributes)); if (!context->attribs) { - free(context); + wfree(context); RErrorCode = RERR_NOMEMORY; return NULL; } @@ -568,7 +570,7 @@ RContext *RCreateContext(Display * dpy, int screen_number, const RContextAttribu templ.visualid = context->attribs->visualid; vinfo = XGetVisualInfo(context->dpy, VisualIDMask | VisualScreenMask, &templ, &nret); if (!vinfo || nret == 0) { - free(context); + wfree(context); RErrorCode = RERR_BADVISUALID; return NULL; } @@ -615,13 +617,13 @@ RContext *RCreateContext(Display * dpy, int screen_number, const RContextAttribu if (context->vclass == PseudoColor || context->vclass == StaticColor) { if (!setupPseudoColorColormap(context)) { - free(context); + wfree(context); return NULL; } } else if (context->vclass == GrayScale || context->vclass == StaticGray) { context->colors = allocateGrayScale(context); if (!context->colors) { - free(context); + wfree(context); return NULL; } } else if (context->vclass == TrueColor) { @@ -668,9 +670,9 @@ void RDestroyContext(RContext *context) if ((context->attribs->flags & RC_VisualID) && !(context->attribs->flags & RC_DefaultVisual)) XDestroyWindow(context->dpy, context->drawable); - free(context->attribs); + wfree(context->attribs); } - free(context); + wfree(context); } } diff --git a/wrlib/convert.c b/wrlib/convert.c index 400234f0..0650fc71 100644 --- a/wrlib/convert.c +++ b/wrlib/convert.c @@ -33,13 +33,15 @@ #include #include +#include + #include "wraster.h" #include "convert.h" #include "xutil.h" #include "wr_i18n.h" -#define NFREE(n) if (n) free(n) +#define NFREE(n) wfree(n) #define HAS_ALPHA(I) ((I)->format == RRGBAFormat) @@ -70,7 +72,7 @@ static void release_conversion_table(void) RConversionTable *tmp_to_delete = tmp; tmp = tmp->next; - free(tmp_to_delete); + wfree(tmp_to_delete); } conversionTable = NULL; } @@ -83,7 +85,7 @@ static void release_std_conversion_table(void) RStdConversionTable *tmp_to_delete = tmp; tmp = tmp->next; - free(tmp_to_delete); + wfree(tmp_to_delete); } stdConversionTable = NULL; } @@ -108,7 +110,7 @@ static unsigned short *computeTable(unsigned short mask) if (tmp) return tmp->table; - tmp = (RConversionTable *) malloc(sizeof(RConversionTable)); + tmp = (RConversionTable *) wmalloc(sizeof(RConversionTable)); if (tmp == NULL) return NULL; @@ -135,7 +137,7 @@ static unsigned int *computeStdTable(unsigned int mult, unsigned int max) if (tmp) return tmp->table; - tmp = (RStdConversionTable *) malloc(sizeof(RStdConversionTable)); + tmp = (RStdConversionTable *) wmalloc(sizeof(RStdConversionTable)); if (tmp == NULL) return NULL; @@ -372,8 +374,8 @@ static RXImage *image2TrueColor(RContext * ctx, RImage * image) signed char *nerr; int ch = (HAS_ALPHA(image) ? 4 : 3); - err = malloc(ch * (image->width + 2)); - nerr = malloc(ch * (image->width + 2)); + err = wmalloc(ch * (image->width + 2)); + nerr = wmalloc(ch * (image->width + 2)); if (!err || !nerr) { NFREE(err); NFREE(nerr); @@ -387,8 +389,8 @@ static RXImage *image2TrueColor(RContext * ctx, RImage * image) convertTrueColor_generic(ximg, image, err, nerr, rtable, gtable, btable, dr, dg, db, roffs, goffs, boffs); - free(err); - free(nerr); + wfree(err); + wfree(nerr); } } @@ -540,8 +542,8 @@ static RXImage *image2PseudoColor(RContext * ctx, RImage * image) #ifdef WRLIB_DEBUG fprintf(stderr, "pseudo color dithering with %d colors per channel\n", cpc); #endif - err = malloc(4 * (image->width + 3)); - nerr = malloc(4 * (image->width + 3)); + err = wmalloc(4 * (image->width + 3)); + nerr = wmalloc(4 * (image->width + 3)); if (!err || !nerr) { NFREE(err); NFREE(nerr); @@ -555,8 +557,8 @@ static RXImage *image2PseudoColor(RContext * ctx, RImage * image) convertPseudoColor_to_8(ximg, image, err + 4, nerr + 4, rtable, gtable, btable, dr, dg, db, ctx->pixels, cpc); - free(err); - free(nerr); + wfree(err); + wfree(nerr); } return ximg; @@ -618,8 +620,8 @@ static RXImage *image2StandardPseudoColor(RContext * ctx, RImage * image) fprintf(stderr, "pseudo color dithering with %d colors per channel\n", ctx->attribs->colors_per_channel); #endif - err = (short *)malloc(3 * (image->width + 2) * sizeof(short)); - nerr = (short *)malloc(3 * (image->width + 2) * sizeof(short)); + err = (short *)wmalloc(3 * (image->width + 2) * sizeof(short)); + nerr = (short *)wmalloc(3 * (image->width + 2) * sizeof(short)); if (!err || !nerr) { NFREE(err); NFREE(nerr); @@ -707,8 +709,8 @@ static RXImage *image2StandardPseudoColor(RContext * ctx, RImage * image) ofs += ximg->image->bytes_per_line - image->width; } - free(err); - free(nerr); + wfree(err); + wfree(nerr); } ximg->image->data = (char *)data; @@ -773,8 +775,8 @@ static RXImage *image2GrayScale(RContext * ctx, RImage * image) #ifdef WRLIB_DEBUG fprintf(stderr, "grayscale dither with %d colors per channel\n", cpc); #endif - gerr = (short *)malloc((image->width + 2) * sizeof(short)); - ngerr = (short *)malloc((image->width + 2) * sizeof(short)); + gerr = (short *)wmalloc((image->width + 2) * sizeof(short)); + ngerr = (short *)wmalloc((image->width + 2) * sizeof(short)); if (!gerr || !ngerr) { NFREE(gerr); NFREE(ngerr); @@ -830,8 +832,8 @@ static RXImage *image2GrayScale(RContext * ctx, RImage * image) gerr = ngerr; ngerr = terr; } - free(gerr); - free(ngerr); + wfree(gerr); + wfree(ngerr); } ximg->image->data = (char *)data; diff --git a/wrlib/convolve.c b/wrlib/convolve.c index 8873a620..7d12fef4 100644 --- a/wrlib/convolve.c +++ b/wrlib/convolve.c @@ -26,6 +26,8 @@ #include #include +#include + #include "wraster.h" #include "wr_i18n.h" @@ -46,7 +48,7 @@ int RBlurImage(RImage * image) unsigned char *pptr = NULL, *tmpp; int ch = image->format == RRGBAFormat ? 4 : 3; - pptr = malloc(image->width * ch); + pptr = wmalloc(image->width * ch); if (!pptr) { RErrorCode = RERR_NOMEMORY; return False; @@ -138,7 +140,7 @@ int RBlurImage(RImage * image) } } - free(tmpp); + wfree(tmpp); return True; } diff --git a/wrlib/load.c b/wrlib/load.c index 1f94f0e4..f9907b66 100644 --- a/wrlib/load.c +++ b/wrlib/load.c @@ -34,6 +34,8 @@ #include #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -125,7 +127,7 @@ static void init_cache(void) RImageCacheMaxImage = IMAGE_CACHE_MAXIMUM_MAXPIXELS; if (RImageCacheSize > 0) { - RImageCache = malloc(sizeof(RCachedImage) * RImageCacheSize); + RImageCache = wmalloc(sizeof(RCachedImage) * RImageCacheSize); if (RImageCache == NULL) { fprintf(stderr, _("wrlib: out of memory for image cache\n")); return; @@ -142,10 +144,10 @@ void RReleaseCache(void) for (i = 0; i < RImageCacheSize; i++) { if (RImageCache[i].file) { RReleaseImage(RImageCache[i].image); - free(RImageCache[i].file); + wfree(RImageCache[i].file); } } - free(RImageCache); + wfree(RImageCache); RImageCache = NULL; RImageCacheSize = -1; } @@ -173,7 +175,7 @@ RImage *RLoadImage(RContext *context, const char *file, int index) return RCloneImage(RImageCache[i].image); } else { - free(RImageCache[i].file); + wfree(RImageCache[i].file); RImageCache[i].file = NULL; RReleaseImage(RImageCache[i].image); } @@ -254,8 +256,7 @@ RImage *RLoadImage(RContext *context, const char *file, int index) for (i = 0; i < RImageCacheSize; i++) { if (!RImageCache[i].file) { - RImageCache[i].file = malloc(strlen(file) + 1); - strcpy(RImageCache[i].file, file); + RImageCache[i].file = wstrdup(file); RImageCache[i].image = RCloneImage(image); RImageCache[i].last_modif = st.st_mtime; RImageCache[i].last_use = time(NULL); @@ -271,10 +272,9 @@ RImage *RLoadImage(RContext *context, const char *file, int index) /* if no slot available, dump least recently used one */ if (!done) { - free(RImageCache[oldest_idx].file); + wfree(RImageCache[oldest_idx].file); RReleaseImage(RImageCache[oldest_idx].image); - RImageCache[oldest_idx].file = malloc(strlen(file) + 1); - strcpy(RImageCache[oldest_idx].file, file); + RImageCache[oldest_idx].file = wstrdup(file); RImageCache[oldest_idx].image = RCloneImage(image); RImageCache[oldest_idx].last_modif = st.st_mtime; RImageCache[oldest_idx].last_use = time(NULL); diff --git a/wrlib/load_gif.c b/wrlib/load_gif.c index 5921735f..870eaf7c 100644 --- a/wrlib/load_gif.c +++ b/wrlib/load_gif.c @@ -28,6 +28,8 @@ #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -127,7 +129,7 @@ RImage *RLoadGIF(const char *file, int index) } } - buffer = malloc(width * sizeof(GifPixelType)); + buffer = wmalloc(width * sizeof(GifPixelType)); if (!buffer) { RErrorCode = RERR_NOMEMORY; goto bye; @@ -219,7 +221,7 @@ RImage *RLoadGIF(const char *file, int index) did_not_get_any_errors: if (buffer) - free(buffer); + wfree(buffer); if (gif) #if (USE_GIF == 5) && (GIFLIB_MINOR >= 1) diff --git a/wrlib/load_jpeg.c b/wrlib/load_jpeg.c index 9c2ef6be..e65fb2d2 100644 --- a/wrlib/load_jpeg.c +++ b/wrlib/load_jpeg.c @@ -35,6 +35,8 @@ #include #endif +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -119,7 +121,7 @@ static RImage *do_read_jpeg_file(struct jpeg_decompress_struct *cinfo, const cha goto abort_and_release_resources; } - buffer[0] = (JSAMPROW) malloc(cinfo->image_width * cinfo->num_components); + buffer[0] = (JSAMPROW) wmalloc(cinfo->image_width * cinfo->num_components); if (!buffer[0]) { RErrorCode = RERR_NOMEMORY; goto abort_and_release_resources; @@ -167,7 +169,7 @@ static RImage *do_read_jpeg_file(struct jpeg_decompress_struct *cinfo, const cha jpeg_destroy_decompress(cinfo); fclose(file); if (buffer[0]) - free(buffer[0]); + wfree(buffer[0]); return image; } diff --git a/wrlib/load_png.c b/wrlib/load_png.c index 50135b51..5eba4ed0 100644 --- a/wrlib/load_png.c +++ b/wrlib/load_png.c @@ -28,6 +28,8 @@ #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -165,7 +167,7 @@ RImage *RLoadPNG(RContext *context, const char *file) image->background.blue = bkcolor->blue >> 8; } - png_rows = calloc(height, sizeof(png_bytep)); + png_rows = wmalloc(height * sizeof(png_bytep)); if (!png_rows) { RErrorCode = RERR_NOMEMORY; fclose(f); @@ -174,7 +176,7 @@ RImage *RLoadPNG(RContext *context, const char *file) return NULL; } for (y = 0; y < height; y++) { - png_rows[y] = malloc(png_get_rowbytes(png, pinfo)); + png_rows[y] = wmalloc(png_get_rowbytes(png, pinfo)); if (!png_rows[y]) { RErrorCode = RERR_NOMEMORY; fclose(f); @@ -182,8 +184,8 @@ RImage *RLoadPNG(RContext *context, const char *file) png_destroy_read_struct(&png, &pinfo, &einfo); while (y-- > 0) if (png_rows[y]) - free(png_rows[y]); - free(png_rows); + wfree(png_rows[y]); + wfree(png_rows); return NULL; } } @@ -214,7 +216,7 @@ RImage *RLoadPNG(RContext *context, const char *file) } for (y = 0; y < height; y++) if (png_rows[y]) - free(png_rows[y]); - free(png_rows); + wfree(png_rows[y]); + wfree(png_rows); return image; } diff --git a/wrlib/load_ppm.c b/wrlib/load_ppm.c index 2f01c8ad..8e817589 100644 --- a/wrlib/load_ppm.c +++ b/wrlib/load_ppm.c @@ -29,6 +29,8 @@ #include #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -161,7 +163,7 @@ static RImage *load_graymap(FILE *file, int w, int h, int max, int raw, const ch if (raw == '5') { char *buf; - buf = malloc(w + 1); + buf = wmalloc(w + 1); if (!buf) { RErrorCode = RERR_NOMEMORY; RReleaseImage(image); @@ -169,7 +171,7 @@ static RImage *load_graymap(FILE *file, int w, int h, int max, int raw, const ch } for (y = 0; y < h; y++) { if (!fread(buf, w, 1, file)) { - free(buf); + wfree(buf); RErrorCode = RERR_BADIMAGEFILE; RReleaseImage(image); return NULL; @@ -181,7 +183,7 @@ static RImage *load_graymap(FILE *file, int w, int h, int max, int raw, const ch *(ptr++) = buf[x]; } } - free(buf); + wfree(buf); } } } diff --git a/wrlib/load_webp.c b/wrlib/load_webp.c index e1a4ee31..c703fb06 100644 --- a/wrlib/load_webp.c +++ b/wrlib/load_webp.c @@ -29,6 +29,8 @@ #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -110,7 +112,7 @@ RImage *RLoadWEBP(const char *file_name) return NULL; } - raw_data = (uint8_t *) malloc(raw_data_size); + raw_data = (uint8_t *) wmalloc(raw_data_size); if (!raw_data) { RErrorCode = RERR_NOMEMORY; @@ -124,7 +126,7 @@ RImage *RLoadWEBP(const char *file_name) if (r != raw_data_size) { RErrorCode = RERR_READ; - free(raw_data); + wfree(raw_data); return NULL; } @@ -133,7 +135,7 @@ RImage *RLoadWEBP(const char *file_name) fprintf(stderr, _("wrlib: could not get features from WebP file \"%s\", %s\n"), file_name, webp_message_from_status(status)); RErrorCode = RERR_BADIMAGEFILE; - free(raw_data); + wfree(raw_data); return NULL; } @@ -141,7 +143,7 @@ RImage *RLoadWEBP(const char *file_name) image = RCreateImage(features.width, features.height, True); if (!image) { RErrorCode = RERR_NOMEMORY; - free(raw_data); + wfree(raw_data); return NULL; } ret = WebPDecodeRGBAInto(raw_data, raw_data_size, image->data, @@ -151,7 +153,7 @@ RImage *RLoadWEBP(const char *file_name) image = RCreateImage(features.width, features.height, False); if (!image) { RErrorCode = RERR_NOMEMORY; - free(raw_data); + wfree(raw_data); return NULL; } ret = WebPDecodeRGBInto(raw_data, raw_data_size, image->data, @@ -159,7 +161,7 @@ RImage *RLoadWEBP(const char *file_name) features.width * 3); } - free(raw_data); + wfree(raw_data); if (!ret) { fprintf(stderr, _("wrlib: failed to decode WebP from file \"%s\"\n"), file_name); diff --git a/wrlib/load_xpm.c b/wrlib/load_xpm.c index 54697acf..d50e7c98 100644 --- a/wrlib/load_xpm.c +++ b/wrlib/load_xpm.c @@ -29,6 +29,8 @@ #include #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -59,11 +61,11 @@ static RImage *create_rimage_from_xpm(RContext *context, XpmImage xpm) /* make color table */ for (i = 0; i < 4; i++) { - color_table[i] = malloc(xpm.ncolors * sizeof(unsigned char)); + color_table[i] = wmalloc(xpm.ncolors * sizeof(unsigned char)); if (!color_table[i]) { for (i = i - 1; i >= 0; i--) { if (color_table[i]) - free(color_table[i]); + wfree(color_table[i]); } RReleaseImage(image); RErrorCode = RERR_NOMEMORY; @@ -123,7 +125,7 @@ static RImage *create_rimage_from_xpm(RContext *context, XpmImage xpm) *(data++) = color_table[3][*p]; } for (i = 0; i < 4; i++) - free(color_table[i]); + wfree(color_table[i]); return image; } diff --git a/wrlib/load_xpm_normalized.c b/wrlib/load_xpm_normalized.c index 82be3c4e..2bbb9176 100644 --- a/wrlib/load_xpm_normalized.c +++ b/wrlib/load_xpm_normalized.c @@ -59,15 +59,15 @@ static void free_color_symbol_table(unsigned char *color_table[], unsigned short *symbol_table) { if (color_table[0]) - free(color_table[0]); + wfree(color_table[0]); if (color_table[1]) - free(color_table[1]); + wfree(color_table[1]); if (color_table[2]) - free(color_table[2]); + wfree(color_table[2]); if (color_table[3]) - free(color_table[3]); + wfree(color_table[3]); if (symbol_table) - free(symbol_table); + wfree(symbol_table); } RImage *RGetImageFromXPMData(RContext * context, char **data) @@ -95,11 +95,11 @@ RImage *RGetImageFromXPMData(RContext * context, char **data) if (csize != 1 && csize != 2) goto bad_format; - color_table[0] = malloc(ccount); - color_table[1] = malloc(ccount); - color_table[2] = malloc(ccount); - color_table[3] = malloc(ccount); - symbol_table = malloc(ccount * sizeof(unsigned short)); + color_table[0] = wmalloc(ccount); + color_table[1] = wmalloc(ccount); + color_table[2] = wmalloc(ccount); + color_table[3] = wmalloc(ccount); + symbol_table = wmalloc(ccount * sizeof(unsigned short)); bsize = csize * w + 16; @@ -283,14 +283,14 @@ RImage *RLoadXPM(RContext * context, const char *file) if (csize != 1 && csize != 2) goto bad_format; - color_table[0] = malloc(ccount); - color_table[1] = malloc(ccount); - color_table[2] = malloc(ccount); - color_table[3] = malloc(ccount); - symbol_table = malloc(ccount * sizeof(unsigned short)); + color_table[0] = wmalloc(ccount); + color_table[1] = wmalloc(ccount); + color_table[2] = wmalloc(ccount); + color_table[3] = wmalloc(ccount); + symbol_table = wmalloc(ccount * sizeof(unsigned short)); bsize = csize * w + 16; - buffer = malloc(bsize); + buffer = wmalloc(bsize); if (!color_table[0] || !color_table[1] || !color_table[2] || !color_table[3] || !symbol_table || !bsize || !buffer) { @@ -298,7 +298,7 @@ RImage *RLoadXPM(RContext * context, const char *file) fclose(f); free_color_symbol_table(color_table, symbol_table); if (buffer) - free(buffer); + wfree(buffer); return NULL; } @@ -355,7 +355,7 @@ RImage *RLoadXPM(RContext * context, const char *file) fclose(f); free_color_symbol_table(color_table, symbol_table); if (buffer) - free(buffer); + wfree(buffer); return NULL; } @@ -434,7 +434,7 @@ RImage *RLoadXPM(RContext * context, const char *file) fclose(f); free_color_symbol_table(color_table, symbol_table); if (buffer) - free(buffer); + wfree(buffer); return image; bad_format: @@ -442,7 +442,7 @@ RImage *RLoadXPM(RContext * context, const char *file) fclose(f); free_color_symbol_table(color_table, symbol_table); if (buffer) - free(buffer); + wfree(buffer); if (image) RReleaseImage(image); return NULL; @@ -452,7 +452,7 @@ RImage *RLoadXPM(RContext * context, const char *file) fclose(f); free_color_symbol_table(color_table, symbol_table); if (buffer) - free(buffer); + wfree(buffer); if (image) RReleaseImage(image); return NULL; diff --git a/wrlib/raster.c b/wrlib/raster.c index 0f8fdd11..14468a21 100644 --- a/wrlib/raster.c +++ b/wrlib/raster.c @@ -27,6 +27,8 @@ #include #include +#include + #include "wraster.h" #include "wr_i18n.h" @@ -53,7 +55,7 @@ RImage *RCreateImage(unsigned width, unsigned height, int alpha) return NULL; } - image = malloc(sizeof(RImage)); + image = wmalloc(sizeof(RImage)); if (!image) { RErrorCode = RERR_NOMEMORY; return NULL; @@ -68,10 +70,10 @@ RImage *RCreateImage(unsigned width, unsigned height, int alpha) /* the +4 is to give extra bytes at the end of the buffer, * so that we can optimize image conversion for MMX(tm).. see convert.c */ - image->data = malloc(width * height * (alpha ? 4 : 3) + 4); + image->data = wmalloc(width * height * (alpha ? 4 : 3) + 4); if (!image->data) { RErrorCode = RERR_NOMEMORY; - free(image); + wfree(image); image = NULL; } @@ -94,8 +96,8 @@ void RReleaseImage(RImage * image) image->refCount--; if (image->refCount < 1) { - free(image->data); - free(image); + wfree(image->data); + wfree(image); } } diff --git a/wrlib/save_jpeg.c b/wrlib/save_jpeg.c index 5320ba47..25c15f6c 100644 --- a/wrlib/save_jpeg.c +++ b/wrlib/save_jpeg.c @@ -29,6 +29,8 @@ #include #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -59,7 +61,7 @@ Bool RSaveJPEG(RImage *img, const char *filename, char *title) img_depth = 3; /* collect separate RGB values to a buffer */ - buffer = malloc(sizeof(char) * 3 * img->width * img->height); + buffer = wmalloc(sizeof(char) * 3 * img->width * img->height); for (y = 0; y < img->height; y++) { for (x = 0; x < img->width; x++) { RGetPixel(img, x, y, &pixel); @@ -97,7 +99,7 @@ Bool RSaveJPEG(RImage *img, const char *filename, char *title) jpeg_finish_compress(&cinfo); /* Clean */ - free(buffer); + wfree(buffer); fclose(file); return True; diff --git a/wrlib/save_png.c b/wrlib/save_png.c index 40d3b996..3ece7cf7 100644 --- a/wrlib/save_png.c +++ b/wrlib/save_png.c @@ -29,6 +29,8 @@ #include #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -95,7 +97,7 @@ Bool RSavePNG(RImage *img, const char *filename, char *title) png_write_info(png_ptr, png_info_ptr); /* Allocate memory for one row (3 bytes per pixel - RGB) */ - png_row = (png_bytep) malloc(3 * width * sizeof(png_byte)); + png_row = (png_bytep) wmalloc(3 * width * sizeof(png_byte)); /* Write image data */ for (y = 0; y < height; y++) { @@ -121,7 +123,7 @@ Bool RSavePNG(RImage *img, const char *filename, char *title) if (png_ptr != NULL) png_destroy_write_struct(&png_ptr, (png_infopp) NULL); if (png_row != NULL) - free(png_row); + wfree(png_row); return True; } diff --git a/wrlib/save_xpm.c b/wrlib/save_xpm.c index 243f9506..2dfae605 100644 --- a/wrlib/save_xpm.c +++ b/wrlib/save_xpm.c @@ -28,6 +28,8 @@ #include #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -95,7 +97,7 @@ static Bool addcolor(XPMColor ** list, unsigned r, unsigned g, unsigned b, int * if (tmpc) return True; - newc = malloc(sizeof(XPMColor)); + newc = wmalloc(sizeof(XPMColor)); if (!newc) { @@ -149,7 +151,7 @@ static void freecolormap(XPMColor * colormap) while (colormap) { tmp = colormap->next; - free(colormap); + wfree(colormap); colormap = tmp; } } diff --git a/wrlib/scale.c b/wrlib/scale.c index 93c5172d..3d5550c4 100644 --- a/wrlib/scale.c +++ b/wrlib/scale.c @@ -29,6 +29,8 @@ #include #include +#include + #include "wraster.h" #include "scale.h" #include "wr_i18n.h" @@ -295,7 +297,7 @@ typedef struct { /* clamp the input to the specified range */ #define CLAMP(v,l,h) ((v)<(l) ? (l) : (v) > (h) ? (h) : v) -/* return of calloc is not checked if NULL in the function below! */ +/* return of wmalloc is not checked if NULL in the function below! */ RImage *RSmoothScaleImage(RImage * src, unsigned new_width, unsigned new_height) { CLIST *contrib; /* array of contribution lists */ @@ -319,13 +321,13 @@ RImage *RSmoothScaleImage(RImage * src, unsigned new_width, unsigned new_height) yscale = (double)new_height / (double)src->height; /* pre-calculate filter contributions for a row */ - contrib = (CLIST *) calloc(new_width, sizeof(CLIST)); + contrib = (CLIST *) wmalloc(new_width * sizeof(CLIST)); if (xscale < 1.0) { width = fwidth / xscale; fscale = 1.0 / xscale; for (i = 0; i < new_width; ++i) { contrib[i].n = 0; - contrib[i].p = (CONTRIB *) calloc((int) ceil(width * 2 + 1), sizeof(CONTRIB)); + contrib[i].p = (CONTRIB *) wmalloc(ceil(width * 2 + 1) * sizeof(CONTRIB)); center = (double)i / xscale; left = ceil(center - width); right = floor(center + width); @@ -348,7 +350,7 @@ RImage *RSmoothScaleImage(RImage * src, unsigned new_width, unsigned new_height) for (i = 0; i < new_width; ++i) { contrib[i].n = 0; - contrib[i].p = (CONTRIB *) calloc((int) ceil(fwidth * 2 + 1), sizeof(CONTRIB)); + contrib[i].p = (CONTRIB *) wmalloc(ceil(fwidth * 2 + 1) * sizeof(CONTRIB)); center = (double)i / xscale; left = ceil(center - fwidth); right = floor(center + fwidth); @@ -395,18 +397,18 @@ RImage *RSmoothScaleImage(RImage * src, unsigned new_width, unsigned new_height) /* free the memory allocated for horizontal filter weights */ for (i = 0; i < new_width; ++i) { - free(contrib[i].p); + wfree(contrib[i].p); } - free(contrib); + wfree(contrib); /* pre-calculate filter contributions for a column */ - contrib = (CLIST *) calloc(dst->height, sizeof(CLIST)); + contrib = (CLIST *) wmalloc(dst->height * sizeof(CLIST)); if (yscale < 1.0) { width = fwidth / yscale; fscale = 1.0 / yscale; for (i = 0; i < dst->height; ++i) { contrib[i].n = 0; - contrib[i].p = (CONTRIB *) calloc((int) ceil(width * 2 + 1), sizeof(CONTRIB)); + contrib[i].p = (CONTRIB *) wmalloc(ceil(width * 2 + 1) * sizeof(CONTRIB)); center = (double)i / yscale; left = ceil(center - width); right = floor(center + width); @@ -428,7 +430,7 @@ RImage *RSmoothScaleImage(RImage * src, unsigned new_width, unsigned new_height) } else { for (i = 0; i < dst->height; ++i) { contrib[i].n = 0; - contrib[i].p = (CONTRIB *) calloc((int) ceil(fwidth * 2 + 1), sizeof(CONTRIB)); + contrib[i].p = (CONTRIB *) wmalloc(ceil(fwidth * 2 + 1) * sizeof(CONTRIB)); center = (double)i / yscale; left = ceil(center - fwidth); right = floor(center + fwidth); @@ -450,7 +452,7 @@ RImage *RSmoothScaleImage(RImage * src, unsigned new_width, unsigned new_height) } /* apply filter to zoom vertically from tmp to dst */ - sp = malloc(tmp->height * 3); + sp = wmalloc(tmp->height * 3); for (k = 0; k < new_width; ++k) { CONTRIB *pp; @@ -485,13 +487,13 @@ RImage *RSmoothScaleImage(RImage * src, unsigned new_width, unsigned new_height) p += new_width * 3; } } - free(sp); + wfree(sp); /* free the memory allocated for vertical filter weights */ for (i = 0; i < dst->height; ++i) { - free(contrib[i].p); + wfree(contrib[i].p); } - free(contrib); + wfree(contrib); RReleaseImage(tmp); diff --git a/wrlib/tests/testgrad.c b/wrlib/tests/testgrad.c index 0b5a7994..9f6a97f3 100644 --- a/wrlib/tests/testgrad.c +++ b/wrlib/tests/testgrad.c @@ -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; @@ -149,10 +149,10 @@ int main(int argc, char **argv) getchar(); - free(color_name); + wfree(color_name); for (i = 0; i < ncolors + 1; i++) - free(colors[i]); - free(colors); + wfree(colors[i]); + wfree(colors); RDestroyContext(ctx); RShutdown(); diff --git a/wrlib/xutil.c b/wrlib/xutil.c index c1fbfeef..7131b4b2 100644 --- a/wrlib/xutil.c +++ b/wrlib/xutil.c @@ -31,6 +31,8 @@ #include +#include + #ifdef USE_XSHM #include #include @@ -63,7 +65,7 @@ RXImage *RCreateXImage(RContext * context, int depth, unsigned width, unsigned h RXImage *rximg; Visual *visual = context->visual; - rximg = malloc(sizeof(RXImage)); + rximg = wmalloc(sizeof(RXImage)); if (!rximg) { RErrorCode = RERR_NOMEMORY; return NULL; @@ -71,14 +73,14 @@ RXImage *RCreateXImage(RContext * context, int depth, unsigned width, unsigned h #ifndef USE_XSHM rximg->image = XCreateImage(context->dpy, visual, depth, ZPixmap, 0, NULL, width, height, 8, 0); if (!rximg->image) { - free(rximg); + wfree(rximg); RErrorCode = RERR_XERROR; return NULL; } - rximg->image->data = malloc(rximg->image->bytes_per_line * height); + rximg->image->data = wmalloc(rximg->image->bytes_per_line * height); if (!rximg->image->data) { XDestroyImage(rximg->image); - free(rximg); + wfree(rximg); RErrorCode = RERR_NOMEMORY; return NULL; } @@ -90,14 +92,14 @@ RXImage *RCreateXImage(RContext * context, int depth, unsigned width, unsigned h rximg->is_shared = 0; rximg->image = XCreateImage(context->dpy, visual, depth, ZPixmap, 0, NULL, width, height, 8, 0); if (!rximg->image) { - free(rximg); + wfree(rximg); RErrorCode = RERR_XERROR; return NULL; } - rximg->image->data = malloc(rximg->image->bytes_per_line * height); + rximg->image->data = wmalloc(rximg->image->bytes_per_line * height); if (!rximg->image->data) { XDestroyImage(rximg->image); - free(rximg); + wfree(rximg); RErrorCode = RERR_NOMEMORY; return NULL; } @@ -173,7 +175,7 @@ void RDestroyXImage(RContext * context, RXImage * rximage) XDestroyImage(rximage->image); } #endif - free(rximage); + wfree(rximage); } static unsigned getDepth(Display * dpy, Drawable d) @@ -205,7 +207,7 @@ RXImage *RGetXImage(RContext * context, Drawable d, int x, int y, unsigned width } } if (!ximg) { - ximg = malloc(sizeof(RXImage)); + ximg = wmalloc(sizeof(RXImage)); if (!ximg) { RErrorCode = RERR_NOMEMORY; return NULL; @@ -214,7 +216,7 @@ RXImage *RGetXImage(RContext * context, Drawable d, int x, int y, unsigned width ximg->image = XGetImage(context->dpy, d, x, y, width, height, AllPlanes, ZPixmap); } #else /* !USE_XSHM */ - ximg = malloc(sizeof(RXImage)); + ximg = wmalloc(sizeof(RXImage)); if (!ximg) { RErrorCode = RERR_NOMEMORY; return NULL; @@ -224,7 +226,7 @@ RXImage *RGetXImage(RContext * context, Drawable d, int x, int y, unsigned width #endif /* !USE_XSHM */ if (ximg->image == NULL) { - free(ximg); + wfree(ximg); return NULL; } diff --git a/wutil-rs/Cargo.toml b/wutil-rs/Cargo.toml new file mode 100644 index 00000000..0d3e85dd --- /dev/null +++ b/wutil-rs/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "wutil-rs" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["staticlib"] + +[build-dependencies] +cc = "1.0" + +[dependencies] +atomic-write-file = "0.3" +hashbrown = "0.16.0" +nom = "8.0" +nom-language = "0.1" +x11 = "2.21.0" diff --git a/wutil-rs/Makefile.am b/wutil-rs/Makefile.am new file mode 100644 index 00000000..00067adb --- /dev/null +++ b/wutil-rs/Makefile.am @@ -0,0 +1,27 @@ +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/prop_list.rs + +RUST_EXTRA = \ + Cargo.lock \ + Cargo.toml + +target/debug/libwutil_rs.a: $(RUST_SOURCES) $(RUST_EXTRA) + $(CARGO) build + +check-local: + $(CARGO) test + +clean-local: + $(CARGO) clean + +all: target/debug/libwutil_rs.a diff --git a/wutil-rs/build.rs b/wutil-rs/build.rs new file mode 100644 index 00000000..58c269c3 --- /dev/null +++ b/wutil-rs/build.rs @@ -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"); +} diff --git a/wutil-rs/src/array.rs b/wutil-rs/src/array.rs new file mode 100644 index 00000000..42a0d45b --- /dev/null +++ b/wutil-rs/src/array.rs @@ -0,0 +1,449 @@ +use std::{ffi::c_void, ptr::NonNull}; + +pub struct Array { + items: Vec>, + destructor: Option, +} + +pub mod ffi { + use super::Array; + + use std::{ + ffi::{c_int, c_void}, + ptr::{self, NonNull}, + }; + + pub const NOT_FOUND: c_int = -1; + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateArray(initial_size: c_int) -> *mut Array { + let cap = if initial_size < 0 { + 0 + } else { + initial_size as usize + }; + Box::leak(Box::new(Array { + items: Vec::with_capacity(cap), + destructor: None, + })) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateArrayWithDestructor( + initial_size: c_int, + destructor: unsafe extern "C" fn(x: *mut c_void), + ) -> *mut Array { + let cap = if initial_size < 0 { + 0 + } else { + initial_size as usize + }; + Box::leak(Box::new(Array { + items: Vec::with_capacity(cap), + destructor: Some(destructor), + })) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateArrayWithArray(array: *mut Array) -> *mut Array { + if array.is_null() { + return ptr::null_mut(); + } + let array = unsafe { &*array }; + Box::leak(Box::new(Array { + items: array.items.clone(), + destructor: array.destructor, + })) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMEmptyArray(array: *mut Array) { + if array.is_null() { + return; + } + let array = unsafe { &mut *array }; + if let Some(f) = array.destructor { + for item in &mut array.items { + unsafe { (f)(item.as_ptr()) } + } + } + array.items.clear(); + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMFreeArray(array: *mut Array) { + if array.is_null() { + return; + } + unsafe { + WMEmptyArray(array); + let _ = ptr::read(array); + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetArrayItemCount(array: *mut Array) -> c_int { + if array.is_null() { + return 0; + } + unsafe { (*array).items.len() as c_int } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMAddToArray(array: *mut Array, item: *mut c_void) { + if array.is_null() { + return; + } + if let Some(item) = NonNull::new(item) { + unsafe { + (*array).items.push(item); + } + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMInsertInArray(array: *mut Array, index: c_int, item: *mut c_void) { + if array.is_null() { + return; + } + if index < 0 { + return; + } + let array = unsafe { &mut (*array).items }; + let index = index as usize; + if index >= array.len() { + return; + } + if let Some(item) = NonNull::new(item) { + array.insert(index, item); + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMSetInArray( + array: *mut Array, + index: c_int, + item: *mut c_void, + ) -> *mut c_void { + if array.is_null() { + return ptr::null_mut(); + } + if index < 0 { + return ptr::null_mut(); + } + let index = index as usize; + + /* is it really useful to perform append if index == array->itemCount ? -Dan */ + if index == unsafe { (*array).items.len() } { + unsafe { + WMAddToArray(array, item); + } + return ptr::null_mut(); + } + + let item = match NonNull::new(item) { + Some(x) => x, + None => return ptr::null_mut(), + }; + let array = unsafe { &mut (*array).items }; + + let old = array[index]; + array[index] = item; + old.as_ptr() + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMDeleteFromArray(array: *mut Array, index: c_int) -> c_int { + if array.is_null() { + return 0; + } + let array = unsafe { &mut *array }; + if index < 0 { + return 0; + } + let index = index as usize; + if index >= array.items.len() { + 0 + } else { + let old = array.items.remove(index); + if let Some(f) = array.destructor { + unsafe { + (f)(old.as_ptr()); + } + } + 1 + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMRemoveFromArray(array: *mut Array, item: *mut c_void) -> c_int { + unsafe { WMRemoveFromArrayMatching(array, None, item) } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMRemoveFromArrayMatching( + array: *mut Array, + pred: Option c_int>, + cdata: *mut c_void, + ) -> c_int { + if array.is_null() { + return 1; + } + let array = unsafe { &mut *array }; + let original_len = array.items.len(); + match pred { + Some(f) => array.items.retain(|x| unsafe { f(x.as_ptr(), cdata) != 0 }), + None => array.items.retain(|x| ptr::eq(x.as_ptr(), cdata)), + } + (original_len - array.items.len()) as c_int + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetFromArray(array: *mut Array, index: c_int) -> *mut c_void { + if array.is_null() || index < 0 { + return ptr::null_mut(); + } + unsafe { + (&(*array)) + .items + .get(index as usize) + .map(|p| p.as_ptr()) + .unwrap_or(ptr::null_mut()) + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetFirstInArray(array: *mut Array, item: *mut c_void) -> c_int { + unsafe { WMFindInArray(array, None, item) } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMPopFromArray(array: *mut Array) -> *mut c_void { + if array.is_null() { + return ptr::null_mut(); + } + unsafe { + (*array) + .items + .pop() + .map(|p| p.as_ptr()) + .unwrap_or(ptr::null_mut()) + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMFindInArray( + array: *mut Array, + pred: Option c_int>, + cdata: *mut c_void, + ) -> c_int { + if array.is_null() { + return NOT_FOUND; + } + let array = unsafe { &*array }; + if let Some(f) = pred { + array + .items + .iter() + .enumerate() + .find(|(_, item)| unsafe { f(item.as_ptr(), cdata) != 0 }) + .map(|(i, _)| i as c_int) + .unwrap_or(NOT_FOUND) + } else { + array + .items + .iter() + .enumerate() + .find(|(_, item)| ptr::eq(item.as_ptr(), cdata)) + .map(|(i, _)| i as c_int) + .unwrap_or(NOT_FOUND) + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCountInArray(array: *mut Array, item: *const c_void) -> c_int { + if array.is_null() { + return 0; + } + let array = unsafe { &*array }; + array + .items + .iter() + .filter(|x| ptr::eq(x.as_ptr(), item)) + .count() as c_int + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMSortArray( + array: *mut Array, + comparator: unsafe extern "C" fn(a: *const c_void, b: *const c_void) -> c_int, + ) { + if array.is_null() { + return; + } + unsafe { + (*array) + .items + .sort_by(|&a, &b| match comparator(a.as_ptr(), b.as_ptr()).signum() { + -1 => std::cmp::Ordering::Less, + 0 => std::cmp::Ordering::Equal, + 1 => std::cmp::Ordering::Greater, + _ => unreachable!(), + }) + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMMapArray( + array: *mut Array, + f: unsafe extern "C" fn(*mut c_void, *mut c_void), + data: *mut c_void, + ) { + if array.is_null() { + return; + } + unsafe { + for a in &mut (*array).items { + (f)(a.as_ptr(), data); + } + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMArrayFirst(array: *mut Array, iter: *mut c_int) -> *mut c_void { + if array.is_null() || iter.is_null() { + return ptr::null_mut(); + } + let array = unsafe { &*array }; + match array.items.get(0) { + None => { + unsafe { + *iter = NOT_FOUND; + } + ptr::null_mut() + } + Some(x) => { + unsafe { + *iter = 0; + } + x.as_ptr() + } + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMArrayLast(array: *mut Array, iter: *mut c_int) -> *mut c_void { + if array.is_null() || iter.is_null() { + return ptr::null_mut(); + } + let array = unsafe { &*array }; + match array.items.last() { + None => { + unsafe { + *iter = NOT_FOUND; + } + ptr::null_mut() + } + Some(x) => { + unsafe { + *iter = (array.items.len() - 1) as c_int; + } + x.as_ptr() + } + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMArrayNext(array: *mut Array, iter: *mut c_int) -> *mut c_void { + if array.is_null() || iter.is_null() { + return ptr::null_mut(); + } + let array = unsafe { &*array }; + let index = unsafe { *iter }; + if index < 0 { + return ptr::null_mut(); + } + match array.items.get(index as usize) { + Some(i) => { + unsafe { + *iter += 1; + } + i.as_ptr() + } + None => { + unsafe { + *iter = NOT_FOUND; + } + ptr::null_mut() + } + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMArrayPrevious(array: *mut Array, iter: *mut c_int) -> *mut c_void { + if array.is_null() || iter.is_null() { + return ptr::null_mut(); + } + let array = unsafe { &*array }; + let index = unsafe { *iter }; + if index < 0 { + return ptr::null_mut(); + } + match array.items.get(index as usize) { + Some(i) => { + unsafe { + *iter -= 1; + } + i.as_ptr() + } + None => { + unsafe { + *iter = NOT_FOUND; + } + ptr::null_mut() + } + } + } +} + +#[cfg(test)] +mod test { + use std::{ffi::c_void, ptr}; + + use super::ffi::*; + + #[test] + fn create_destroy_with_size() { + unsafe { + let array = WMCreateArray(10); + assert_eq!((*array).items.len(), 0); + assert!((*array).items.capacity() >= 10); + WMFreeArray(array); + } + } + + #[test] + fn create_push_clear_destroy() { + static mut SENTINEL: *mut c_void = ptr::null_mut(); + unsafe extern "C" fn destructor(item: *mut c_void) { + unsafe { + SENTINEL = item; + } + } + unsafe { + let array = WMCreateArrayWithDestructor(10, destructor); + assert!(SENTINEL.is_null()); + + let mut x = 0xdeadbeefu32; + WMAddToArray(array, (&mut x as *mut u32).cast::()); + assert_eq!(WMGetArrayItemCount(array), 1); + WMEmptyArray(array); + assert!(ptr::eq(SENTINEL, (&x as *const u32).cast::())); + assert_eq!(0xdeadbeefu32, *SENTINEL.cast::()); + + SENTINEL = ptr::null_mut(); + WMFreeArray(array); + assert!(SENTINEL.is_null()); + } + } +} diff --git a/wutil-rs/src/data.rs b/wutil-rs/src/data.rs new file mode 100644 index 00000000..dab84a49 --- /dev/null +++ b/wutil-rs/src/data.rs @@ -0,0 +1,209 @@ +//! Self-owning shared data segment. + +use std::{cell::RefCell, rc::Rc}; + +#[derive(Clone, Copy, Debug)] +pub enum Format { + Z = 0, + E = 8, + S = 16, + T = 32, +} + +/// Reference-counted, self-owned, dynamically sized chunk of bytes. +/// +/// In the original WINGs, this type either owned or borrowed a data buffer and +/// had some associated metadata. In Rust, this is little more than a thin +/// wrapper around an `Rc>>`. It is mostly used by proplists, +/// and it should be done away with once its dependents have been ported to +/// Rust. +pub struct Data(Rc>); + +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(&self, f: impl FnOnce(&[u8]) -> R) -> R { + f(&self.0.borrow().bytes) + } +} + +struct Inner { + bytes: Vec, + format: Format, +} + +pub mod ffi { + use super::{Data, Format, Inner}; + + use std::{ + cell::RefCell, + ffi::{c_int, c_uint, c_void}, + ptr, + rc::Rc, + }; + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateDataWithCapacity(capacity: c_uint) -> *mut Data { + Box::leak(Box::new(Data(Rc::new(RefCell::new(Inner { + bytes: Vec::with_capacity(capacity as usize), + format: Format::Z, + }))))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateDataWithLength(length: c_uint) -> *mut Data { + Box::leak(Box::new(Data(Rc::new(RefCell::new(Inner { + bytes: vec![0; length as usize], + format: Format::Z, + }))))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateDataWithBytes( + bytes: *const c_void, + length: c_uint, + ) -> *mut Data { + let bytes = unsafe { &*ptr::slice_from_raw_parts(bytes.cast::(), length as usize) }; + let bytes = Vec::from(bytes); + Box::leak(Box::new(Data(Rc::new(RefCell::new(Inner { + bytes, + format: Format::Z, + }))))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateDataWithData(data: *mut Data) -> *mut Data { + if data.is_null() { + return ptr::null_mut(); + } + let data = unsafe { &*data }; + Box::leak(Box::new(Data(Rc::new(RefCell::new(Inner { + bytes: data.0.borrow().bytes.clone(), + format: data.0.borrow().format, + }))))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMRetainData(data: *mut Data) -> *mut Data { + if data.is_null() { + return ptr::null_mut(); + } + let data = unsafe { &*data }; + Box::leak(Box::new(Data(data.0.clone()))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMReleaseData(data: *mut Data) { + if data.is_null() { + return; + } + let _ = unsafe { ptr::read(data) }; + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMDataBytes(data: *mut Data) -> *const c_void { + if data.is_null() { + return ptr::null(); + } + + unsafe { (*data).0.borrow().bytes.as_ptr().cast::() } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMIsDataEqualToData(a: *mut Data, b: *mut Data) -> c_int { + if a.is_null() || b.is_null() { + return 0; + } + if ptr::eq(a, b) { + return 1; + } + let a = unsafe { &*a }; + let b = unsafe { &*b }; + (a.0.borrow().bytes == b.0.borrow().bytes) as c_int + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetDataLength(data: *mut Data) -> c_uint { + if data.is_null() { + return 0; + } + unsafe { (*data).0.borrow().bytes.len() as c_uint } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMAppendDataBytes( + data: *mut Data, + bytes: *const c_void, + length: c_uint, + ) { + if data.is_null() || bytes.is_null() || length == 0 { + return; + } + let data = unsafe { &mut *data }; + let bytes = unsafe { &*ptr::slice_from_raw_parts(bytes.cast::(), length as usize) }; + data.0.borrow_mut().bytes.extend_from_slice(bytes); + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMAppendData(data: *mut Data, ext: *mut Data) { + if data.is_null() || ext.is_null() { + return; + } + let data = unsafe { &mut *data }; + let ext = unsafe { &*ext }; + data.0 + .borrow_mut() + .bytes + .extend_from_slice(&ext.0.borrow().bytes); + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMSetData(data: *mut Data, other: *mut Data) { + if data.is_null() || other.is_null() { + return; + } + let data = unsafe { &mut *data }; + let other = unsafe { &*other }; + data.0 + .borrow_mut() + .bytes + .copy_from_slice(&other.0.borrow().bytes); + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetDataFormat(data: *mut Data) -> c_uint { + if data.is_null() { + return 0; + } + return unsafe { + match (*data).0.borrow().format { + Format::Z => 0, + Format::E => 8, + Format::S => 16, + Format::T => 32, + } + }; + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMSetDataFormat(data: *mut Data, format: c_uint) { + if data.is_null() { + return; + } + let format = match format { + 0 => Format::Z, + 8 => Format::E, + 16 => Format::S, + 32 => Format::T, + _ => return, + }; + unsafe { + (*data).0.borrow_mut().format = format; + } + } +} diff --git a/wutil-rs/src/defines.c b/wutil-rs/src/defines.c new file mode 100644 index 00000000..e75292f9 --- /dev/null +++ b/wutil-rs/src/defines.c @@ -0,0 +1,5 @@ +#include "../../config-paths.h" + +const char *get_GSUSER_SUBDIR() { + return GSUSER_SUBDIR; +} diff --git a/wutil-rs/src/defines.rs b/wutil-rs/src/defines.rs new file mode 100644 index 00000000..678c5883 --- /dev/null +++ b/wutil-rs/src/defines.rs @@ -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 { + 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()); + } +} diff --git a/wutil-rs/src/find_file.rs b/wutil-rs/src/find_file.rs new file mode 100644 index 00000000..9e86d0f7 --- /dev/null +++ b/wutil-rs/src/find_file.rs @@ -0,0 +1,395 @@ +//! This module provides approximate reimplementations of file-finding routines +//! from the original WINGs utilities. +//! +//! The [`ffi`] submodule provides functions which may be called directly from C +//! that has not yet been ported to Rust. +//! +//! The original utilities expanded environment variables in path names +//! (expanding `$FOO/bar/baz` to use the value of the environment variable +//! `FOO`) and respected Unix-style denotations of user home directories +//! (resolving `~someuser/foo.txt` to `(home directory of +//! someuser)/foo.txt`. These behaviors have not been preserved. But a path +//! whose first component is `~` will still be resolved relatively to the +//! current user's home directory. +//! +//! ## 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::{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 { + 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 +/// the user's home directory cannot be determined, `/` is used instead. +/// +/// Returns `None` otherwise. +pub fn absolute(file: &Path) -> Option { + if file.is_absolute() { + return Some(file.to_path_buf()); + } else { + let mut components = file.components(); + if components.next() == Some(Component::Normal(OsStr::new("~"))) { + let mut path = env::home_dir().unwrap_or_else(|| PathBuf::from("/")); + for c in components { + path.push(c); + } + Some(path) + } else { + None + } + } +} + +/// Resolves `file` to a path that can be opened relative to an element of +/// `paths`, or `None` if it cannot be found. If `paths` is empty, an attempt to +/// resolve `file` relative to the current working directory will be made. +pub fn in_paths<'a>(paths: impl Iterator, file: &Path) -> Option { + if file.file_name().map(|f| f.is_empty()).unwrap_or(false) { + return None; + } + let mut paths = paths.peekable(); + + if paths.peek().is_none() { + if let Ok(_) = File::open(file) { + return Some(file.to_path_buf()); + } + return None; + } + + let mut buf = PathBuf::new(); + for parent in paths { + buf.clear(); + buf.push(parent); + buf.push(file); + if let Ok(_) = File::open(&buf) { + return Some(buf); + } + } + 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 { + 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: 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: 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, create_path_hierarchy, in_paths, path_from_cstr, remove_path_hierarchy, user_gnustep_path}; + use crate::memory::alloc_bytes; + + use std::{ + env, + ffi::{c_char, c_int, CStr, OsStr}, + iter, + os::unix::ffi::OsStrExt, + path::{Path, PathBuf}, + ptr, + }; + + fn split_paths(paths: &CStr) -> impl Iterator { + paths.to_bytes().split(|b| *b == b':') + } + + fn to_c_str(p: &Path) -> *mut c_char { + let os_bytes = p.as_os_str().as_encoded_bytes(); + let buf = alloc_bytes(os_bytes.len() + 1); + unsafe { + ptr::copy_nonoverlapping(os_bytes.as_ptr(), buf, os_bytes.len()); + } + buf.cast::() + } + + /// Attempts to find `file` under colon-separated `paths`. Checks if `file` + /// is absolute or prefixed with `~` before attempting to resolve it + /// relatively. If no file can be found, returns NULL. Non-NULL return + /// values must be freed with [`crate::memory::free_bytes`] or + /// [`crate::memory::ffi::wfree`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wfindfile(paths: *const c_char, file: *const c_char) -> *mut c_char { + if file.is_null() { + return ptr::null_mut(); + } + let file = unsafe { CStr::from_ptr(file) }; + let file = Path::new(OsStr::from_bytes(file.to_bytes())); + if let Some(path) = absolute(&file) { + return to_c_str(&path); + } + let path = if paths.is_null() { + in_paths(iter::empty(), file) + } else { + let paths = unsafe { CStr::from_ptr(paths) }; + in_paths( + split_paths(paths).map(|p| Path::new(OsStr::from_bytes(p))), + file, + ) + }; + path.map(|x| to_c_str(x.as_ref())) + .unwrap_or(ptr::null_mut()) + } + + /// Attempts to find `file` under an element of NULL-terminated + /// `path_list`. Checks if `file` is absolute or prefixed with `~` before + /// attempting to resolve it relatively. If no file can be found, returns + /// NULL. Non-NULL return values must be freed with + /// [`crate::memory::free_bytes`] or [`crate::memory::ffi::wfree`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wfindfileinlist( + path_list: *const *const c_char, + file: *const c_char, + ) -> *mut c_char { + if file.is_null() { + return ptr::null_mut(); + } + let file = unsafe { CStr::from_ptr(file) }; + let file = Path::new(OsStr::from_bytes(file.to_bytes())); + if let Some(path) = absolute(&file) { + return to_c_str(&path); + } + + let path = if path_list.is_null() { + in_paths(iter::empty(), file) + } else { + let paths = (0usize..) + .map(|offset| unsafe { path_list.add(offset) }) + .take_while(|&p| unsafe { !(*p).is_null() }) + .map(|p| Path::new(OsStr::from_bytes(unsafe { CStr::from_ptr(*p).to_bytes() }))); + in_paths(paths, file) + }; + + path.map(|x| to_c_str(x.as_ref())) + .unwrap_or(ptr::null_mut()) + } + + /// Attempts to expand `path` if it starts with `~` by replacing the first + /// path element with the user's home directory. Returns NULL if `path` is + /// NULL. Non-NULL return values must be freed with + /// [`crate::memory::free_bytes`] or [`crate::memory::ffi::wfree`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wexpandpath(path: *const c_char) -> *mut c_char { + if path.is_null() { + return ptr::null_mut(); + } + let path = unsafe { CStr::from_ptr(path) }; + let path = Path::new(OsStr::from_bytes(path.to_bytes())); + absolute(path) + .map(|p| to_c_str(&p)) + .unwrap_or_else(|| to_c_str(&path)) + } + + /// Returns the home directory of the current user, or `"/"` if it cannot be + /// determined. The returned value must be freed with + /// [`crate::memory::free_bytes`] or [`crate::memory::ffi::wfree`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wgethomedir() -> *mut c_char { + match env::home_dir() { + Some(x) => to_c_str(x.as_ref()), + None => to_c_str(Path::new("/")), + } + } + + /// Copies `src_file` into `dest_dir/dest_file`. Returns 0 on success, or -1 + /// on error. + /// + /// This is provided solely to support code that has not yet been ported to + /// Rust. Prefer `std::fs::copy` or another utility if you can. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wcopy_file( + dest_dir: *const c_char, + src_file: *const c_char, + dest_file: *const c_char, + ) -> c_int { + if dest_dir.is_null() || src_file.is_null() || dest_file.is_null() { + return -1; + } + let src_file = unsafe { CStr::from_ptr(src_file) }; + let dest_dir = unsafe { CStr::from_ptr(dest_dir) }; + let dest_file = unsafe { CStr::from_ptr(dest_file) }; + let src = Path::new(OsStr::from_bytes(src_file.to_bytes())); + let mut dest = PathBuf::from(OsStr::from_bytes(dest_dir.to_bytes())); + dest.push(OsStr::from_bytes(dest_file.to_bytes())); + if std::fs::copy(src, dest).is_ok() { + return 0; + } else { + 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() + } + } +} diff --git a/wutil-rs/src/hash_table.rs b/wutil-rs/src/hash_table.rs new file mode 100644 index 00000000..77c20823 --- /dev/null +++ b/wutil-rs/src/hash_table.rs @@ -0,0 +1,294 @@ +use hashbrown::hash_map::{self, HashMap}; + +use std::{ + ffi::CStr, + hash::{Hash, Hasher}, + mem, +}; + +pub enum HashTable { + PointerKeyed(HashMap<*mut u8, *mut u8>), + StringKeyed(HashMap), +} + +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 u8) -> Option<*mut u8> { + match self { + HashTable::PointerKeyed(m) => { + let key = key.cast_mut(); + m.get(&key).copied() + } + HashTable::StringKeyed(m) => { + let key = StringKey(key.cast_mut()); + m.get(&key).copied() + } + } + } + + pub unsafe fn insert(&mut self, key: *mut u8, data: *mut u8) -> Option<*mut u8> { + match self { + HashTable::PointerKeyed(m) => m.insert(key, data), + HashTable::StringKeyed(m) => m.insert(StringKey(key), data), + } + } + + pub unsafe fn remove(&mut self, key: *const u8) { + match self { + HashTable::PointerKeyed(m) => { + let key = key.cast_mut(); + m.remove(&key); + } + HashTable::StringKeyed(m) => { + let key = StringKey(key.cast_mut()); + m.remove(&key); + mem::forget(key); + } + } + } +} + +#[derive(Debug)] +#[repr(transparent)] +pub struct StringKey(*const u8); + +impl PartialEq for StringKey { + fn eq(&self, other: &Self) -> bool { + match (self.0.is_null(), other.0.is_null()) { + (true, true) => true, + (false, false) => unsafe { CStr::from_ptr(self.0.cast()) == CStr::from_ptr(other.0.cast()) }, + _ => false, + } + } +} + +impl Eq for StringKey {} + +impl Hash for StringKey { + fn hash(&self, h: &mut H) { + if self.0.is_null() { + h.write_usize(0) + } else { + unsafe { CStr::from_ptr(self.0.cast()).hash(h) } + } + } +} + +pub enum Enumerator<'a> { + PointerKeyed(hash_map::IterMut<'a, *mut u8, *mut u8>), + StringKeyed(hash_map::IterMut<'a, StringKey, *mut u8>), +} + +pub mod ffi { + use std::{ + ffi::{c_int, c_uint, c_void}, + mem, ptr, + }; + + use super::{Enumerator, HashTable, StringKey}; + + #[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::(); + (unsafe { (*table).get(key) }) + .map(|v| v.cast::()) + .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 result = match m.get_key_value_mut(&key.cast::().cast_mut()) { + Some((k, v)) => { + unsafe { + *key_dest = k.cast::(); + *value_dest = v.cast::(); + } + 1 + } + None => 0, + }; + result + } + HashTable::StringKeyed(m) => { + let key = StringKey(key.cast::().cast_mut()); + let result = match m.get_key_value_mut(&key) { + Some((k, v)) => { + unsafe { + *key_dest = k.0.cast::(); + *value_dest = v.cast::(); + } + 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::(), data.cast::()) } { + Some(v) => v.cast::(), + 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::()); + } + } + + /// 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.cast::(), + None => ptr::null_mut(), + }, + Enumerator::StringKeyed(i) => match i.next() { + Some((_, v)) => v.cast::(), + None => ptr::null_mut(), + }, + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMNextHashEnumeratorKey(e: *mut Enumerator<'static>) -> *const 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.cast::(), + None => ptr::null_mut(), + }, + Enumerator::StringKeyed(i) => match i.next() { + Some((k, _)) => k.0.cast::(), + None => ptr::null_mut(), + }, + } + } +} diff --git a/wutil-rs/src/lib.rs b/wutil-rs/src/lib.rs new file mode 100644 index 00000000..68a17ca2 --- /dev/null +++ b/wutil-rs/src/lib.rs @@ -0,0 +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; diff --git a/wutil-rs/src/memory.rs b/wutil-rs/src/memory.rs new file mode 100644 index 00000000..b5aae74b --- /dev/null +++ b/wutil-rs/src/memory.rs @@ -0,0 +1,283 @@ +//! Custom implementations of malloc/free/realloc. +//! +//! These are intended for use by C functions that need to allocate. Window +//! Maker originally provided [`wmalloc`], [`wfree`], and [`wrealloc`] for +//! customizable handling of memory exhaustion (to save workspace state before +//! aborting) and to allow optional use of the Boehm GC library. It also tracked +//! reference counts, via [`wretain`] and [`wrelease`]. +//! +//! If everything gets rewritten in Rust, we won't need this module anymore. For +//! now, it helps to move our allocations into Rust so that it is more +//! straightforward to store Rust objects in heap memory that was allocated from +//! C. (Rust may have stricter requirements for heap-allocated segments than are +//! provided by arbitrary C allocators). +//! +//! TODO: We may want to restore handling of OOM errors. This would require +//! installing a customized Rust allocator, which isn't something you can do yet +//! in stable Rust. And, unless our rewrite ends up taking up obscenely more +//! memory than the baseline Window Maker code, it isn't really necessary in +//! this day and age. + +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, + payload_size: usize, + layout: alloc::Layout, + refcount: u16, +} + +impl Header { + /// Recovers the `Header` for the allocated memory chunk `b`. + /// + /// ## Safety + /// + /// Callers must ensure that `b` is a live allocation from [`wmalloc`] or [`wrealloc`]. + unsafe fn for_alloc_bytes(b: *mut u8) -> *mut Header { + unsafe { + b.sub(mem::size_of::
()) + .cast::
() + } + } +} + +/// Allocates at least `size` bytes and returns a pointer to them. +/// +/// Returns null if `size` is 0. +pub fn alloc_bytes(size: usize) -> *mut u8 { + if size == 0 { + return ptr::null_mut(); + } + let Ok(header_layout) = alloc::Layout::from_size_align(mem::size_of::
(), 8) else { + return ptr::null_mut(); + }; + + let Ok(layout) = alloc::Layout::from_size_align(size, 8) else { + 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(full_layout) }; + if full_segment.is_null() { + return ptr::null_mut(); + } + let result = unsafe { full_segment.add(result_offset) }; + if result.is_null() { + return ptr::null_mut(); + } + + unsafe { + let header = result.sub(mem::size_of::
()).cast::
(); + header.write_unaligned(Header { + ptr: NonNull::new_unchecked(full_segment), + payload_size: size, + layout: full_layout, + refcount: 0, + }); + } + + 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::(); + unsafe { ptr::copy_nonoverlapping(s.as_ptr().cast::(), result, len); } + result +} + +/// Frees the bytes pointed to by `b`. +/// +/// ## Safety +/// +/// Callers must ensure that `b` is a live allocation from [`wmalloc`] or [`wrealloc`]. +pub unsafe fn free_bytes(b: *mut u8) { + if b.is_null() { + return; + } + unsafe { + let header = &*Header::for_alloc_bytes(b); + alloc::dealloc(header.ptr.as_ptr(), header.layout); + } +} + +/// Functions to be called from C. +pub mod ffi { + use super::{alloc_bytes, free_bytes, Header}; + + use std::{ffi::c_void, ptr}; + + /// Allocates `size` bytes. Returns null if `size` is 0. Data will be + /// initialized to 0. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wmalloc(size: usize) -> *mut c_void { + alloc_bytes(size).cast::() + } + + /// Frees `ptr`, which must have come from [`wmalloc`] or [`wrealloc`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wfree(ptr: *mut c_void) { + unsafe { free_bytes(ptr.cast::()); } + } + + /// Resizes `ptr` to be at least `newsize` bytes in size, returning the + /// 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 + /// + /// 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); + result + } + } + + /// Bumps the refcount for `ptr`. + /// + /// ## Safety + /// + /// Callers must ensure that `b` is a live allocation from [`wmalloc`] or [`wrealloc`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wretain(ptr: *mut c_void) -> *mut c_void { + if ptr.is_null() { + return ptr::null_mut(); + } + unsafe { + let header = Header::for_alloc_bytes(ptr.cast::()); + (*header).refcount += 1; + } + ptr + } + + /// Decrements the refcount for `ptr`. If this brings the refcount to 0, + /// frees `ptr`. + /// + /// ## Safety + /// + /// Callers must ensure that `ptr` is a live allocation from [`wmalloc`] or [`wrealloc`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wrelease(ptr: *mut c_void) { + let ptr = ptr.cast::(); + if ptr.is_null() { + return; + } + let header = unsafe { &mut *Header::for_alloc_bytes(ptr) }; + match header.refcount { + 0 | 1 => unsafe { free_bytes(ptr) }, + _ => header.refcount -= 1, + } + } +} + +#[cfg(test)] +mod test { + use super::{alloc_bytes, alloc_string, ffi::{wfree, wmalloc, wrealloc}, free_bytes, Header}; + + use std::{ffi::CStr, mem, os::raw::c_void, ptr, slice}; + + #[test] + fn recover_header() { + unsafe { + let x = alloc_bytes(mem::size_of::()); + let header = Header::for_alloc_bytes(x); + assert_eq!(header.cast::().add(mem::size_of::
()), x); + // This may be allocator-dependent, but it's a reasonable sanity check for now. + assert!((*header).ptr.as_ptr() <= header.cast::()); + } + } + + #[test] + fn alloc_zero_returns_null() { + assert!(alloc_bytes(0).is_null()); + } + + #[test] + fn free_null() { + unsafe { free_bytes(ptr::null_mut()); } + } + + #[test] + fn realloc_null() { + unsafe { assert!(wrealloc(ptr::null_mut(), 0).is_null()); } + } + + #[test] + fn alloc_free_nonzero() { + let x = alloc_bytes(mem::size_of::()).cast::(); + assert!(!x.is_null()); + unsafe { *x = 42; } + assert_eq!(unsafe { *x }, 42); + unsafe { free_bytes(x.cast::()); } + } + + #[test] + fn multiple_allocs() { + unsafe { + let x = wmalloc(mem::size_of::()); + *x.cast::() = 30; + let y = wmalloc(mem::size_of::()); + *y.cast::() = 5; + let z = wmalloc(48); + *z.cast::() = 1.0; + wfree(x); + wfree(y); + wfree(z); + } + } + + #[test] + fn realloc_nonzero() { + let x = alloc_bytes(mem::size_of::()).cast::(); + assert!(!x.is_null()); + let y = unsafe { wrealloc(x, mem::size_of::()).cast::() }; + assert!(!y.is_null()); + unsafe { *y = 17; } + assert_eq!(unsafe { *y }, 17); + unsafe { free_bytes(y.cast::()); } + } + + #[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::>()); + } + } + + #[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::()); } + } +} diff --git a/wutil-rs/src/prop_list.rs b/wutil-rs/src/prop_list.rs new file mode 100644 index 00000000..c5b488b2 --- /dev/null +++ b/wutil-rs/src/prop_list.rs @@ -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`-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), +} + +impl hash::Hash for Node { + fn hash(&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>`. 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>); + +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>(path: P) -> Result { + 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>(command: S) -> Result { + 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(&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>(&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::() + } 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()); + } +} diff --git a/wutil-rs/src/prop_list/parser.rs b/wutil-rs/src/prop_list/parser.rs new file mode 100644 index 00000000..de542047 --- /dev/null +++ b/wutil-rs/src/prop_list/parser.rs @@ -0,0 +1,1367 @@ +//! Support for reading NeXTSTEP/OPENSTEP/Window Maker property list files. +//! +//! Files are parsed into the WINGs-internal [`PropList`] structure, which does +//! not support GNUstep extensions for special data types (`NSValue`, `NSDate`, +//! etc.) or comments. +//! +//! This is not exactly the same as the original WINGs parser, but it should +//! handle Window Maker configurations just fine. Notable differences include: +//! +//! * A single trailing backslash in quoted strings is not accepted (and a +//! properly escaped backslash must be used). +//! * Strings are UTF-8 (and input must be in UTF-8). Files with strings that +//! contain a zero byte will be rejected because we still use C-style strings +//! internally to support FFI with code that has not yet been ported to Rust. + +use std::{collections::HashMap, ffi::CString}; + +use nom::{ + branch::alt, + character::complete::{char, multispace0, none_of, satisfy}, + combinator::{cut, eof, fail, map, map_res, opt}, + error::context, + multi::{fold_many0, many1, many_m_n, separated_list0}, + sequence::{delimited, preceded, terminated}, + AsChar, Finish, IResult, Input, Parser, +}; +use nom_language::error::{convert_error, VerboseError}; + +use super::{Node, PropList}; + +fn read_dictionary_key>(input: I) -> IResult> { + preceded( + multispace0, + terminated( + read_string, + cut(context( + "looking for '=' after dictionary key", + (multispace0, char('='), multispace0), + )), + ), + ) + .parse(input) +} + +fn read_dictionary_value>( + input: I, +) -> IResult> { + terminated( + delimited(multispace0, read_prop_list, multispace0), + cut(context( + "looking for ';' after dictionary value", + (char(';'), multispace0), + )), + ) + .parse(input) +} + +fn read_dictionary>(input: I) -> IResult> { + delimited( + context( + "looking for dictionary to start with '{'", + (multispace0, char('{'), multispace0), + ), + map( + fold_many0( + (read_dictionary_key, read_dictionary_value), + HashMap::new, + |mut items: HashMap, (k, v): (PropList, PropList)| { + items.insert(k, v); + items + }, + ), + |items: HashMap| PropList::new(Node::Dictionary(items)), + ), + cut(context( + "looking for dictionary to end with '}'", + (multispace0, char('}'), multispace0), + )), + ) + .parse(input) +} + +fn read_array_elements>( + input: I, +) -> IResult, VerboseError> { + delimited( + multispace0, + separated_list0( + context("looking for comma between array elements", char(',')), + delimited(multispace0, read_prop_list, multispace0), + ), + multispace0, + ) + .parse(input) +} + +fn read_array>(input: I) -> IResult> { + map( + preceded( + (multispace0, char('(')), + terminated( + read_array_elements, + ( + multispace0, + opt(char(',')), + multispace0, + cut(context("looking for array that ends with ')'", char(')'))), + ), + ), + ), + |items: Vec| PropList::new(Node::Array(items)), + ) + .parse(input) +} + +fn unescape_character(c: char) -> char { + match c { + '\\' => '\\', + '"' => '"', + 'a' => '\x07', + 'b' => '\x08', + 't' => '\t', + 'n' => '\n', + 'v' => '\x0B', + 'f' => '\x0D', + x @ _ => x, + } +} + +fn unescape_octal(bytes: Vec) -> Result { + let mut result = 0u32; + for b in &bytes { + result <<= 3; + result |= (*b as u32) & 0o7; + } + char::from_u32(result).ok_or_else(|| { + format!( + "unable to convert octal escape sequence '{:?}' to character", + &bytes + ) + }) +} + +fn read_quoted_char>(input: I) -> IResult> { + alt(( + preceded( + char('\\'), + cut(alt(( + map( + context( + "looking for an escaped character", + satisfy(|c: char| !c.is_oct_digit()), + ), + unescape_character, + ), + map_res( + context( + "looking for an octal sequence (0-7, up to 3 times)", + many_m_n(1, 3, satisfy(|c| c.is_oct_digit())), + ), + unescape_octal, + ), + ))), + ), + // Characters that do not need escaping. + none_of(r#""\"#), + )) + .parse(input) +} + +fn read_nullbyte_char>( + input: I, +) -> IResult> { + map_res(read_quoted_char, |c: char| { + let mut bytes = [0u8; 4]; + let s = c.encode_utf8(&mut bytes).as_bytes(); + for b in s { + if *b == 0 { + let len = s.len(); + return Ok((bytes, len)); + } + } + Err(()) + }) + .parse(input) +} + +fn read_char_bytes>( + input: I, +) -> IResult> { + map(read_quoted_char, |c: char| { + let mut bytes = [0u8; 4]; + let s = c.encode_utf8(&mut bytes).as_bytes(); + let len = s.len(); + (bytes, len) + }) + .parse(input) +} + +fn read_quoted_string>(input: I) -> IResult> { + map( + fold_many0( + alt(( + terminated( + // First we try to read a char with a null byte. If we do, + // unconditionally fail. + read_nullbyte_char, + cut(context( + "null bytes are not allowed in strings", + fail::>(), + )), + ), + // Character is valid, so proceed. + read_char_bytes, + )), + Vec::new, + |mut buf: Vec, (bytes, len): ([u8; 4], usize)| { + buf.extend_from_slice(&bytes[0..len]); + buf + }, + ), + |buf: Vec| unsafe { CString::from_vec_unchecked(buf) }, + ) + .parse(input) +} + +pub(crate) fn is_unquoted_char(c: char) -> bool { + c.is_ascii_alphanumeric() || c == '.' || c == '_' || c == '/' || c == '+' +} + +fn read_unquoted_string>(input: I) -> IResult> { + map( + many1(map(satisfy(is_unquoted_char), |c: char| c as u8)), + |bytes: Vec| unsafe { CString::from_vec_unchecked(bytes) }, + ) + .parse(input) +} + +fn read_string>(input: I) -> IResult> { + map( + delimited( + multispace0, + alt(( + delimited( + char('"'), + read_quoted_string, + cut(context("looking for closing \" for string", char('"'))), + ), + read_unquoted_string, + )), + multispace0, + ), + |s: CString| PropList::new(Node::String(s)), + ) + .parse(input) +} + +fn read_prop_list>(input: I) -> IResult> { + delimited( + multispace0, + alt((read_array, read_dictionary, read_string)), + multispace0, + ) + .parse(input) +} + +fn is_context_error(e: &nom_language::error::VerboseErrorKind) -> bool { + if let nom_language::error::VerboseErrorKind::Context(_) = e { + true + } else { + false + } +} + +/// Reads a `PropList` from `s`. Returns a human-readable error message if +/// there's an error. +pub fn from_str(s: &str) -> Result { + terminated( + read_prop_list, + ( + multispace0, + cut(context( + "looking for complete property list at start of input, with no trailing garbage", + eof, + )), + ), + ) + .parse(s) + .finish() + .map(|(_, plist)| plist) + .map_err(|e| { + if let Some(e) = e.errors.iter().filter(|(_, e)| is_context_error(e)).last() { + // Drop all but the deepest context error from the VerboseError trace. + convert_error( + s, + VerboseError { + errors: vec![e.clone()], + }, + ) + } else { + convert_error(s, e) + } + }) +} + +#[cfg(test)] +mod test { + use std::ffi::CString; + + use crate::prop_list::{parser::from_str, Node, PropList}; + + fn pl_array(xs: Vec) -> PropList { + PropList::new(Node::Array(xs)) + } + + fn pl_dict(kvs: Vec<(PropList, PropList)>) -> PropList { + PropList::new(Node::Dictionary(kvs.into_iter().collect())) + } + + fn pl_string(s: &str) -> PropList { + PropList::new(Node::String(CString::new(s.as_bytes()).unwrap())) + } + + #[test] + fn parse_string() { + let plist = from_str("foobar").unwrap(); + assert_eq!(plist, pl_string("foobar")); + } + + #[test] + fn parse_escaped_string() { + let plist = from_str(r#""\t""#).unwrap(); + assert_eq!(plist, pl_string("\t")); + } + + #[test] + fn parse_embedded_escaped_string() { + let plist = from_str(r#""hello\t world""#).unwrap(); + assert_eq!(plist, pl_string("hello\t world")); + + let plist = from_str(r#""hello, \v world! \\^_^\/ ""#).unwrap(); + assert_eq!(plist, pl_string("hello, \x0B world! \\^_^/ ")); + } + + #[test] + fn parse_octal_escaped_string() { + let plist = from_str(r#""\3""#).unwrap(); + assert_eq!(plist, pl_string("\x03")); + + let plist = from_str(r#""\11""#).unwrap(); + assert_eq!(plist, pl_string("\x09")); + + let plist = from_str(r#""\011""#).unwrap(); + assert_eq!(plist, pl_string("\x09")); + + let plist = from_str(r#""\107""#).unwrap(); + assert_eq!(plist, pl_string("G")); + } + + #[test] + fn parse_octal_invalid_chars() { + let plist = from_str(r#""\99""#).unwrap(); + assert_eq!(plist, pl_string("99")); + + let plist = from_str(r#""hello\8t""#).unwrap(); + assert_eq!(plist, pl_string("hello8t")); + } + + #[test] + fn error_unclosed_string() { + let e = from_str(r#""hello, world!"#).unwrap_err(); + assert_eq!( + e, + r#"0: at line 1, in looking for closing " for string: +"hello, world! + ^ + +"# + ); + } + + #[test] + fn error_null_byte_not_allowed_in_string() { + let e = from_str(r#""hello\00world""#).unwrap_err(); + assert_eq!( + format!("{}", e), + r#"0: at line 1, in null bytes are not allowed in strings: +"hello\00world" + ^ + +"# + ); + } + + #[test] + fn parse_flat_array() { + let plist = from_str( + r#" +(hello, + world, + blah) +"#, + ) + .unwrap(); + assert_eq!( + plist, + pl_array(vec![ + pl_string("hello"), + pl_string("world"), + pl_string("blah") + ]), + ) + } + + #[test] + fn parse_empty_array() { + let plist = from_str("()").unwrap(); + assert_eq!(plist, pl_array(vec![])); + + let plist = from_str(" () ").unwrap(); + assert_eq!(plist, pl_array(vec![])); + } + + #[test] + fn parse_shallow_array() { + let plist = from_str(r#"(deeper, (what), what)"#).unwrap(); + assert_eq!( + plist, + pl_array(vec![ + pl_string("deeper"), + pl_array(vec![pl_string("what")]), + pl_string("what") + ]) + ); + } + + #[test] + fn parse_nested_array() { + let plist = from_str( + r#" + (hello, + "world?? \7", + (what, "'s the deal", etc., + (but, (we, (can, (go, (deeper, (what, (is, (this, lisp, or, something))), "???"))))) ), +)"#, + ) + .unwrap(); + assert_eq!( + plist, + pl_array(vec![ + pl_string("hello"), + pl_string("world?? \x07"), + pl_array(vec![ + pl_string("what"), + pl_string("'s the deal"), + pl_string("etc."), + pl_array(vec![ + pl_string("but"), + pl_array(vec![ + pl_string("we"), + pl_array(vec![ + pl_string("can"), + pl_array(vec![ + pl_string("go"), + pl_array(vec![ + pl_string("deeper"), + pl_array(vec![ + pl_string("what"), + pl_array(vec![ + pl_string("is"), + pl_array(vec![ + pl_string("this"), + pl_string("lisp"), + pl_string("or"), + pl_string("something"), + ]) + ]) + ]), + pl_string("???"), + ]) + ]) + ]) + ]) + ]) + ]) + ]) + ); + } + + #[test] + fn error_unclosed_array() { + let e = from_str("(hello, world, stuff, blah").unwrap_err(); + assert_eq!( + e, + r#"0: at line 1, in looking for array that ends with ')': +(hello, world, stuff, blah + ^ + +"# + ); + } + + #[test] + fn parse_empty_dictionary() { + let plist = from_str("{}").unwrap(); + assert_eq!(plist, pl_dict(vec![])); + + let plist = from_str("{ }").unwrap(); + assert_eq!(plist, pl_dict(vec![])); + + let plist = from_str( + r#" +{ +} "#, + ) + .unwrap(); + assert_eq!(plist, pl_dict(vec![])); + } + + #[test] + fn parse_flat_dictionary() { + let plist = from_str(r#"{ Clip = (); "Hello" = "World!"; }"#).unwrap(); + assert_eq!( + plist, + pl_dict(vec![ + (pl_string("Clip"), pl_array(vec![])), + (pl_string("Hello"), pl_string("World!")) + ]) + ); + } + + #[test] + fn error_unterminated_dictionary() { + let e = from_str(r#"{ Clip = (); Hello = world; Workspaces = (); "#).unwrap_err(); + assert_eq!( + e, + r#"0: at line 1, in looking for dictionary to end with '}': +{ Clip = (); Hello = world; Workspaces = (); + ^ + +"# + ); + } + + #[test] + fn error_dictionary_missing_kv_separator() { + let e = from_str(r#"{ Clip = (); Hello world; }"#).unwrap_err(); + assert_eq!( + e, + r#"0: at line 1, in looking for '=' after dictionary key: +{ Clip = (); Hello world; } + ^ + +"# + ); + } + + #[test] + fn error_dictionary_missing_kv_terminator() { + let e = from_str(r#"{ Clip = (); Hello = world"#).unwrap_err(); + assert_eq!( + e, + r#"0: at line 1, in looking for ';' after dictionary value: +{ Clip = (); Hello = world + ^ + +"# + ); + } + + #[test] + fn parse_wm_root_menu() { + let plist = from_str( + r#"( + Debian, + ( + Applications, + OPEN_PLMENU, + "|| wmmenugen -parser:xdg /usr/share/applications/" + ), + (Run..., EXEC, "%A(Run,Type command to run)"), + (Terminal, SHORTCUT, "Mod4+Return", EXEC, "/usr/bin/urxvtcd"), + (Lock, SHORTCUT, "Mod4+l", EXEC, "/usr/bin/xscreensaver-command -lock"), + (Firefox, SHORTCUT, "Mod4+b", EXEC, "/usr/bin/firefox"), + (Emacsclient, SHORTCUT, "Mod4+e", EXEC, "/usr/bin/emacsclient -"), + (bluetoothctl, EXEC, "/home/stu/bin/bluetoothctl-term"), + ( + "Window Maker", + ("Info Panel ...", INFO_PANEL), + ("Legal Panel ...", LEGAL_PANEL), + (Preferences, EXEC, WPrefs), + ("Refresh screen", REFRESH), + (Restart, RESTART) + ), + ( + WorkSpace, + (Appearance, OPEN_MENU, appearance.menu), + ("Arrange Icons", ARRANGE_ICONS), + ("Clear Session", CLEAR_SESSION), + ("Hide Others", HIDE_OTHERS), + ("Save Session", SAVE_SESSION), + ("Show All", SHOW_ALL), + (Workspaces, WORKSPACE_MENU) + ), + (Exit, EXIT), + ("Exit session", SHUTDOWN) +) +"#, + ) + .unwrap(); + assert_eq!( + plist, + pl_array(vec![ + pl_string("Debian"), + pl_array(vec![ + pl_string("Applications"), + pl_string("OPEN_PLMENU"), + pl_string("|| wmmenugen -parser:xdg /usr/share/applications/") + ]), + pl_array(vec![ + pl_string("Run..."), + pl_string("EXEC"), + pl_string("%A(Run,Type command to run)") + ]), + pl_array(vec![ + pl_string("Terminal"), + pl_string("SHORTCUT"), + pl_string("Mod4+Return"), + pl_string("EXEC"), + pl_string("/usr/bin/urxvtcd") + ]), + pl_array(vec![ + pl_string("Lock"), + pl_string("SHORTCUT"), + pl_string("Mod4+l"), + pl_string("EXEC"), + pl_string("/usr/bin/xscreensaver-command -lock") + ]), + pl_array(vec![ + pl_string("Firefox"), + pl_string("SHORTCUT"), + pl_string("Mod4+b"), + pl_string("EXEC"), + pl_string("/usr/bin/firefox") + ]), + pl_array(vec![ + pl_string("Emacsclient"), + pl_string("SHORTCUT"), + pl_string("Mod4+e"), + pl_string("EXEC"), + pl_string("/usr/bin/emacsclient -") + ]), + pl_array(vec![ + pl_string("bluetoothctl"), + pl_string("EXEC"), + pl_string("/home/stu/bin/bluetoothctl-term") + ]), + pl_array(vec![ + pl_string("Window Maker"), + pl_array(vec![pl_string("Info Panel ..."), pl_string("INFO_PANEL")]), + pl_array(vec![pl_string("Legal Panel ..."), pl_string("LEGAL_PANEL")]), + pl_array(vec![ + pl_string("Preferences"), + pl_string("EXEC"), + pl_string("WPrefs") + ]), + pl_array(vec![pl_string("Refresh screen"), pl_string("REFRESH")]), + pl_array(vec![pl_string("Restart"), pl_string("RESTART")]) + ]), + pl_array(vec![ + pl_string("WorkSpace"), + pl_array(vec![ + pl_string("Appearance"), + pl_string("OPEN_MENU"), + pl_string("appearance.menu") + ]), + pl_array(vec![pl_string("Arrange Icons"), pl_string("ARRANGE_ICONS")]), + pl_array(vec![pl_string("Clear Session"), pl_string("CLEAR_SESSION")]), + pl_array(vec![pl_string("Hide Others"), pl_string("HIDE_OTHERS")]), + pl_array(vec![pl_string("Save Session"), pl_string("SAVE_SESSION")]), + pl_array(vec![pl_string("Show All"), pl_string("SHOW_ALL")]), + pl_array(vec![pl_string("Workspaces"), pl_string("WORKSPACE_MENU")]) + ]), + pl_array(vec![pl_string("Exit"), pl_string("EXIT")]), + pl_array(vec![pl_string("Exit session"), pl_string("SHUTDOWN")]) + ]) + ); + } + + #[test] + fn parse_wm_state() { + let plist = from_str( + 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; + }, + ); +} +"#, + ) + .unwrap(); + + assert_eq!( + plist, + pl_dict(vec![ + ( + pl_string("Dock"), + pl_dict(vec![ + (pl_string("AutoRaiseLower"), pl_string("No")), + ( + pl_string("Applications"), + pl_array(vec![ + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("Logo.WMDock")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("No")), + (pl_string("Position"), pl_string("0,0")), + (pl_string("Lock"), pl_string("Yes")), + (pl_string("Command"), pl_string("/usr/bin/WPrefs")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("wmweather.wmweather")), + (pl_string("DropCommand"), pl_string("wmweather -s KBOS %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("Yes")), + (pl_string("Position"), pl_string("0,4")), + (pl_string("Lock"), pl_string("Yes")), + (pl_string("PasteCommand"), pl_string("wmweather -s KBOS %s")), + (pl_string("Command"), pl_string("wmweather -s KBOS")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("wmclock.WMClock")), + (pl_string("DropCommand"), pl_string("wmclock %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("Yes")), + (pl_string("Position"), pl_string("0,3")), + (pl_string("Lock"), pl_string("Yes")), + (pl_string("PasteCommand"), pl_string("wmclock %s")), + (pl_string("Command"), pl_string("wmclock")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("emacs.Emacs")), + (pl_string("DropCommand"), pl_string("emacsclient -c %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("No")), + (pl_string("Position"), pl_string("0,2")), + (pl_string("Lock"), pl_string("Yes")), + (pl_string("PasteCommand"), pl_string("emacsclient -c %s")), + (pl_string("Command"), pl_string("emacsclient -")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("wmfire.wmfire")), + (pl_string("DropCommand"), pl_string("wmfire -m %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("Yes")), + (pl_string("Position"), pl_string("0,5")), + (pl_string("Lock"), pl_string("Yes")), + (pl_string("PasteCommand"), pl_string("wmfire -m %s")), + (pl_string("Command"), pl_string("wmfire -m")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("wmnet.WMNET")), + (pl_string("DropCommand"), pl_string("wmnet -W enp0s31f6 %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("Yes")), + (pl_string("Position"), pl_string("0,7")), + (pl_string("Lock"), pl_string("Yes")), + ( + pl_string("PasteCommand"), + pl_string("wmnet -W enp0s31f6 %s") + ), + (pl_string("Command"), pl_string("wmnet -W enp0s31f6")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("wmtemp.DockApp")), + (pl_string("DropCommand"), pl_string("wmtemp -f %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("Yes")), + (pl_string("Position"), pl_string("0,6")), + (pl_string("Lock"), pl_string("Yes")), + (pl_string("PasteCommand"), pl_string("wmtemp -f %s")), + (pl_string("Command"), pl_string("wmtemp -f")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("firefox.Firefox")), + (pl_string("DropCommand"), pl_string("firefox %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("No")), + (pl_string("Position"), pl_string("0,1")), + (pl_string("Lock"), pl_string("Yes")), + (pl_string("PasteCommand"), pl_string("firefox %s")), + (pl_string("Command"), pl_string("firefox")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + ( + pl_string("Name"), + pl_string("org\\.gnome\\.Weather.org\\.gnome\\.Weather") + ), + (pl_string("DropCommand"), pl_string("org.gnome.Weather %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("No")), + (pl_string("Position"), pl_string("0,8")), + (pl_string("Lock"), pl_string("Yes")), + (pl_string("PasteCommand"), pl_string("org.gnome.Weather %s")), + (pl_string("Command"), pl_string("/usr/bin/gnome-weather")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("Zotero.Zotero")), + ( + pl_string("DropCommand"), + pl_string("/home/stu/bin/zotero %d") + ), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("No")), + (pl_string("Position"), pl_string("0,9")), + (pl_string("Lock"), pl_string("Yes")), + ( + pl_string("PasteCommand"), + pl_string("/home/stu/bin/zotero %s") + ), + (pl_string("Command"), pl_string("/home/stu/bin/zotero")), + ]), + ]) + ), + (pl_string("Lowered"), pl_string("Yes")), + (pl_string("Position"), pl_string("2496,0")), + ( + pl_string("Applications1440"), + pl_array(vec![ + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("Logo.WMDock")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("No")), + (pl_string("Position"), pl_string("0,0")), + (pl_string("Lock"), pl_string("Yes")), + (pl_string("Command"), pl_string("/usr/bin/WPrefs")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("wmweather.wmweather")), + (pl_string("DropCommand"), pl_string("wmweather -s KBOS %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("Yes")), + (pl_string("Position"), pl_string("0,4")), + (pl_string("Lock"), pl_string("Yes")), + (pl_string("PasteCommand"), pl_string("wmweather -s KBOS %s")), + (pl_string("Command"), pl_string("wmweather -s KBOS")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("wmclock.WMClock")), + (pl_string("DropCommand"), pl_string("wmclock %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("Yes")), + (pl_string("Position"), pl_string("0,3")), + (pl_string("Lock"), pl_string("Yes")), + (pl_string("PasteCommand"), pl_string("wmclock %s")), + (pl_string("Command"), pl_string("wmclock")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("emacs.Emacs")), + (pl_string("DropCommand"), pl_string("emacsclient -c %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("No")), + (pl_string("Position"), pl_string("0,2")), + (pl_string("Lock"), pl_string("Yes")), + (pl_string("PasteCommand"), pl_string("emacsclient -c %s")), + (pl_string("Command"), pl_string("emacsclient -")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("wmfire.wmfire")), + (pl_string("DropCommand"), pl_string("wmfire -m %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("Yes")), + (pl_string("Position"), pl_string("0,5")), + (pl_string("Lock"), pl_string("Yes")), + (pl_string("PasteCommand"), pl_string("wmfire -m %s")), + (pl_string("Command"), pl_string("wmfire -m")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("wmnet.WMNET")), + (pl_string("DropCommand"), pl_string("wmnet -W enp0s31f6 %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("Yes")), + (pl_string("Position"), pl_string("0,7")), + (pl_string("Lock"), pl_string("Yes")), + ( + pl_string("PasteCommand"), + pl_string("wmnet -W enp0s31f6 %s") + ), + (pl_string("Command"), pl_string("wmnet -W enp0s31f6")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("wmtemp.DockApp")), + (pl_string("DropCommand"), pl_string("wmtemp -f %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("Yes")), + (pl_string("Position"), pl_string("0,6")), + (pl_string("Lock"), pl_string("Yes")), + (pl_string("PasteCommand"), pl_string("wmtemp -f %s")), + (pl_string("Command"), pl_string("wmtemp -f")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("firefox.Firefox")), + (pl_string("DropCommand"), pl_string("firefox %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("No")), + (pl_string("Position"), pl_string("0,1")), + (pl_string("Lock"), pl_string("Yes")), + (pl_string("PasteCommand"), pl_string("firefox %s")), + (pl_string("Command"), pl_string("firefox")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + ( + pl_string("Name"), + pl_string("org\\.gnome\\.Weather.org\\.gnome\\.Weather") + ), + (pl_string("DropCommand"), pl_string("org.gnome.Weather %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("No")), + (pl_string("Position"), pl_string("0,8")), + (pl_string("Lock"), pl_string("Yes")), + (pl_string("PasteCommand"), pl_string("org.gnome.Weather %s")), + (pl_string("Command"), pl_string("/usr/bin/gnome-weather")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("Zotero.Zotero")), + ( + pl_string("DropCommand"), + pl_string("/home/stu/bin/zotero %d") + ), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("No")), + (pl_string("Position"), pl_string("0,9")), + (pl_string("Lock"), pl_string("Yes")), + ( + pl_string("PasteCommand"), + pl_string("/home/stu/bin/zotero %s") + ), + (pl_string("Command"), pl_string("/home/stu/bin/zotero")), + ]) + ]) + ) + ]) + ), + ( + pl_string("Clip"), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("Logo.WMClip")), + (pl_string("DropCommand"), pl_string("wmsetbg -u -t %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("No")), + (pl_string("Position"), pl_string("2496,1376")), + (pl_string("Lock"), pl_string("No")), + (pl_string("Command"), pl_string("-")), + ]) + ), + (pl_string("Drawers"), pl_array(vec![])), + ( + pl_string("Workspaces"), + pl_array(vec![ + pl_dict(vec![ + ( + pl_string("Clip"), + pl_dict(vec![ + (pl_string("AutoRaiseLower"), pl_string("No")), + (pl_string("AutoCollapse"), pl_string("No")), + ( + pl_string("Applications"), + pl_array(vec![ + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + (pl_string("Name"), pl_string("xsnow.Xsnow")), + (pl_string("DropCommand"), pl_string("xsnow %d")), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("No")), + (pl_string("Position"), pl_string("-2,0")), + (pl_string("Lock"), pl_string("No")), + (pl_string("PasteCommand"), pl_string("xsnow %s")), + (pl_string("Command"), pl_string("xsnow")), + (pl_string("Omnipresent"), pl_string("No")), + ]), + pl_dict(vec![ + (pl_string("Forced"), pl_string("No")), + ( + pl_string("Name"), + pl_string("thunderbird.Thunderbird-default") + ), + ( + pl_string("DropCommand"), + pl_string("thunderbird %d") + ), + (pl_string("BuggyApplication"), pl_string("No")), + (pl_string("AutoLaunch"), pl_string("No")), + (pl_string("Position"), pl_string("-1,0")), + (pl_string("Lock"), pl_string("No")), + ( + pl_string("PasteCommand"), + pl_string("thunderbird %s") + ), + (pl_string("Command"), pl_string("thunderbird")), + (pl_string("Omnipresent"), pl_string("No")), + ]) + ]) + ), + (pl_string("Collapsed"), pl_string("No")), + (pl_string("AutoAttractIcons"), pl_string("No")), + (pl_string("Lowered"), pl_string("Yes")) + ]) + ), + (pl_string("Name"), pl_string("Mail")), + ]), + pl_dict(vec![ + ( + pl_string("Clip"), + pl_dict(vec![ + (pl_string("AutoRaiseLower"), pl_string("No")), + (pl_string("AutoCollapse"), pl_string("No")), + (pl_string("Applications"), pl_array(vec![])), + (pl_string("Collapsed"), pl_string("No")), + (pl_string("AutoAttractIcons"), pl_string("No")), + (pl_string("Lowered"), pl_string("Yes")), + ]) + ), + (pl_string("Name"), pl_string("Plans")), + ]), + pl_dict(vec![ + ( + pl_string("Clip"), + pl_dict(vec![ + (pl_string("AutoRaiseLower"), pl_string("No")), + (pl_string("AutoCollapse"), pl_string("No")), + (pl_string("Applications"), pl_array(vec![])), + (pl_string("Collapsed"), pl_string("No")), + (pl_string("AutoAttractIcons"), pl_string("No")), + (pl_string("Lowered"), pl_string("Yes")), + ]) + ), + (pl_string("Name"), pl_string("Misc")), + ]), + ]) + ) + ]), + ); + } + + #[test] + fn error_trailing_garbage() { + let e = from_str(r#"good bad"#).unwrap_err(); + assert_eq!(e, "0: at line 1, in looking for complete property list at start of input, with no trailing garbage: +good bad + ^ + +"); + } +} diff --git a/wutil-rs/src/prop_list/writer.rs b/wutil-rs/src/prop_list/writer.rs new file mode 100644 index 00000000..52f517c2 --- /dev/null +++ b/wutil-rs/src/prop_list/writer.rs @@ -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> { + /// 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> Display { + /// 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> fmt::Display for Display { + 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::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); + } +}