shell32: Add trashing support functions.
diff --git a/dlls/shell32/Makefile.in b/dlls/shell32/Makefile.in
index 4ab01f1..58376e9 100644
--- a/dlls/shell32/Makefile.in
+++ b/dlls/shell32/Makefile.in
@@ -48,7 +48,9 @@
shpolicy.c \
shv_bg_cmenu.c \
shv_item_cmenu.c \
- systray.c
+ systray.c \
+ trash.c \
+ xdg.c
RC_SRCS = shres.rc
RC_BINSRC = shres.rc
diff --git a/dlls/shell32/trash.c b/dlls/shell32/trash.c
new file mode 100644
index 0000000..595ab08
--- /dev/null
+++ b/dlls/shell32/trash.c
@@ -0,0 +1,308 @@
+/*
+ * The freedesktop.org Trash, implemented using the 0.7 spec version
+ * (see http://www.ramendik.ru/docs/trashspec.html)
+ *
+ * Copyright (C) 2006 Mikolaj Zalewski
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include <stdarg.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "winerror.h"
+#include "winreg.h"
+#include "shlwapi.h"
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <time.h>
+#include "wine/debug.h"
+#include "shell32_main.h"
+#include "xdg.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(trash);
+
+static CRITICAL_SECTION TRASH_Creating;
+static CRITICAL_SECTION_DEBUG TRASH_Creating_Debug =
+{
+ 0, 0, &TRASH_Creating,
+ { &TRASH_Creating_Debug.ProcessLocksList,
+ &TRASH_Creating_Debug.ProcessLocksList},
+ 0, 0, { (DWORD_PTR)__FILE__ ": TRASH_Creating"}
+};
+static CRITICAL_SECTION TRASH_Creating = { &TRASH_Creating_Debug, -1, 0, 0, 0, 0 };
+
+static const char trashinfo_suffix[] = ".trashinfo";
+static const char trashinfo_header[] = "[Trash Info]\n";
+
+typedef struct
+{
+ char *info_dir;
+ char *files_dir;
+ dev_t device;
+} TRASH_BUCKET;
+
+static TRASH_BUCKET *home_trash=NULL;
+
+static char *init_home_dir(const char *subpath)
+{
+ char *path = XDG_BuildPath(XDG_DATA_HOME, subpath);
+ if (path == NULL) return NULL;
+ if (!XDG_MakeDirs(path))
+ {
+ ERR("Couldn't create directory %s (errno=%d). Trash won't be available\n", debugstr_a(path), errno);
+ SHFree(path);
+ path=NULL;
+ }
+ return path;
+}
+
+static TRASH_BUCKET *TRASH_CreateHomeBucket(void)
+{
+ TRASH_BUCKET *bucket;
+ struct stat trash_stat;
+ char *trash_path = NULL;
+
+ bucket = SHAlloc(sizeof(TRASH_BUCKET));
+ if (bucket == NULL)
+ {
+ errno = ENOMEM;
+ goto error;
+ }
+ memset(bucket, 0, sizeof(*bucket));
+ bucket->info_dir = init_home_dir("Trash/info/");
+ if (bucket->info_dir == NULL) goto error;
+ bucket->files_dir = init_home_dir("Trash/files/");
+ if (bucket->files_dir == NULL) goto error;
+
+ trash_path = XDG_BuildPath(XDG_DATA_HOME, "Trash/");
+ if (stat(trash_path, &trash_stat) == -1)
+ goto error;
+ bucket->device = trash_stat.st_dev;
+ SHFree(trash_path);
+ return bucket;
+error:
+ SHFree(trash_path);
+ if (bucket)
+ {
+ SHFree(bucket->info_dir);
+ SHFree(bucket->files_dir);
+ }
+ SHFree(bucket);
+ return NULL;
+}
+
+static BOOL TRASH_EnsureInitialized(void)
+{
+ if (home_trash == NULL)
+ {
+ EnterCriticalSection(&TRASH_Creating);
+ if (home_trash == NULL)
+ home_trash = TRASH_CreateHomeBucket();
+ LeaveCriticalSection(&TRASH_Creating);
+ }
+
+ if (home_trash == NULL)
+ {
+ ERR("Couldn't initialize home trash (errno=%d)\n", errno);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL file_good_for_bucket(TRASH_BUCKET *pBucket, struct stat *file_stat)
+{
+ if (pBucket->device != file_stat->st_dev)
+ return FALSE;
+ return S_ISREG(file_stat->st_mode);
+}
+
+BOOL TRASH_CanTrashFile(LPCWSTR wszPath)
+{
+ struct stat file_stat;
+ char *unix_path;
+
+ TRACE("(%s)\n", debugstr_w(wszPath));
+ if (!TRASH_EnsureInitialized()) return FALSE;
+ if (!(unix_path = wine_get_unix_file_name(wszPath)))
+ return FALSE;
+ if (lstat(unix_path, &file_stat)==-1)
+ {
+ HeapFree(GetProcessHeap(), 0, unix_path);
+ return FALSE;
+ }
+ HeapFree(GetProcessHeap(), 0, unix_path);
+ return file_good_for_bucket(home_trash, &file_stat);
+}
+
+/*
+ * Try to create a single .trashinfo file. Return TRUE if successfull, else FALSE
+ */
+static BOOL try_create_trashinfo_file(const char *info_dir, const char *file_name,
+ const char *original_file_name)
+{
+ struct tm curr_time;
+ time_t curr_time_secs;
+ char datebuf[200];
+ char *path = SHAlloc(strlen(info_dir)+strlen(file_name)+strlen(trashinfo_suffix)+1);
+ int writer = -1;
+
+ if (path==NULL) return FALSE;
+ wsprintfA(path, "%s%s%s", info_dir, file_name, trashinfo_suffix);
+ TRACE("Trying to create '%s'\n", path);
+ writer = open(path, O_CREAT|O_WRONLY|O_TRUNC|O_EXCL, 0600);
+ if (writer==-1) goto error;
+
+ write(writer, trashinfo_header, strlen(trashinfo_header));
+ if (!XDG_WriteDesktopStringEntry(writer, "Path", XDG_URLENCODE, original_file_name))
+ goto error;
+
+ time(&curr_time_secs);
+ localtime_r(&curr_time_secs, &curr_time);
+ wnsprintfA(datebuf, 200, "%04d-%02d-%02dT%02d:%02d:%02d",
+ curr_time.tm_year+1900,
+ curr_time.tm_mon+1,
+ curr_time.tm_mday,
+ curr_time.tm_hour,
+ curr_time.tm_min,
+ curr_time.tm_sec);
+ if (!XDG_WriteDesktopStringEntry(writer, "DeletionDate", 0, datebuf))
+ goto error;
+ close(writer);
+ SHFree(path);
+ return TRUE;
+
+error:
+ if (writer != -1)
+ {
+ close(writer);
+ unlink(path);
+ }
+ SHFree(path);
+ return FALSE;
+}
+
+/*
+ * Try to create a .trashinfo file. This function will make several attempts with
+ * different filenames. It will return the filename that succeded or NULL if a file
+ * couldn't be created.
+ */
+static char *create_trashinfo(const char *info_dir, const char *file_path)
+{
+ const char *base_name;
+ char *filename_buffer;
+ unsigned int seed = (unsigned int)time(NULL);
+ int i;
+
+ errno = ENOMEM; /* out-of-memory is the only case when errno isn't set */
+ base_name = strrchr(file_path, '/');
+ if (base_name == NULL)
+ base_name = file_path;
+ else
+ base_name++;
+
+ filename_buffer = SHAlloc(strlen(base_name)+9+1);
+ if (filename_buffer == NULL)
+ return NULL;
+ lstrcpyA(filename_buffer, base_name);
+ if (try_create_trashinfo_file(info_dir, filename_buffer, file_path))
+ return filename_buffer;
+ for (i=0; i<30; i++)
+ {
+ sprintf(filename_buffer, "%s-%d", base_name, i+1);
+ if (try_create_trashinfo_file(info_dir, filename_buffer, file_path))
+ return filename_buffer;
+ }
+
+ for (i=0; i<1000; i++)
+ {
+ sprintf(filename_buffer, "%s-%08x", base_name, rand_r(&seed));
+ if (try_create_trashinfo_file(info_dir, filename_buffer, file_path))
+ return filename_buffer;
+ }
+
+ WARN("Couldn't create trashinfo after 1031 tries (errno=%d)\n", errno);
+ SHFree(filename_buffer);
+ return NULL;
+}
+
+void remove_trashinfo_file(const char *info_dir, const char *base_name)
+{
+ char *filename_buffer;
+
+ filename_buffer = SHAlloc(lstrlenA(info_dir)+lstrlenA(base_name)+lstrlenA(trashinfo_suffix)+1);
+ if (filename_buffer == NULL) return;
+ sprintf(filename_buffer, "%s%s%s", info_dir, base_name, trashinfo_suffix);
+ unlink(filename_buffer);
+ SHFree(filename_buffer);
+}
+
+static BOOL TRASH_MoveFileToBucket(TRASH_BUCKET *pBucket, const char *unix_path)
+{
+ struct stat file_stat;
+ char *trash_file_name = NULL;
+ char *trash_path = NULL;
+ BOOL ret = TRUE;
+
+ if (lstat(unix_path, &file_stat)==-1)
+ return FALSE;
+ if (!file_good_for_bucket(pBucket, &file_stat))
+ return FALSE;
+
+ trash_file_name = create_trashinfo(pBucket->info_dir, unix_path);
+ if (trash_file_name == NULL)
+ return FALSE;
+
+ trash_path = SHAlloc(strlen(pBucket->files_dir)+strlen(trash_file_name)+1);
+ if (trash_path == NULL) goto error;
+ lstrcpyA(trash_path, pBucket->files_dir);
+ lstrcatA(trash_path, trash_file_name);
+
+ if (rename(unix_path, trash_path)==0)
+ {
+ TRACE("rename succeded\n");
+ goto cleanup;
+ }
+
+ /* TODO: try to manually move the file */
+ ERR("Couldn't move file\n");
+error:
+ ret = FALSE;
+ remove_trashinfo_file(pBucket->info_dir, trash_file_name);
+cleanup:
+ SHFree(trash_file_name);
+ SHFree(trash_path);
+ return ret;
+}
+
+BOOL TRASH_TrashFile(LPCWSTR wszPath)
+{
+ char *unix_path;
+ BOOL result;
+
+ TRACE("(%s)\n", debugstr_w(wszPath));
+ if (!TRASH_EnsureInitialized()) return FALSE;
+ if (!(unix_path = wine_get_unix_file_name(wszPath)))
+ return FALSE;
+ result = TRASH_MoveFileToBucket(home_trash, unix_path);
+ HeapFree(GetProcessHeap(), 0, unix_path);
+ return result;
+}
diff --git a/dlls/shell32/xdg.c b/dlls/shell32/xdg.c
new file mode 100644
index 0000000..7166650
--- /dev/null
+++ b/dlls/shell32/xdg.c
@@ -0,0 +1,372 @@
+/*
+ * Generic freedesktop.org support code
+ *
+ * Copyright (C) 2006 Mikolaj Zalewski
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "winreg.h"
+#include "shlwapi.h"
+#include "wine/debug.h"
+#include "shell32_main.h"
+#include "xdg.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(xdg);
+
+/*
+ * XDG paths implemented using Desktop Base Directory spec version 0.6
+ * (from http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html)
+ */
+
+static CRITICAL_SECTION XDG_PathsLock;
+static CRITICAL_SECTION_DEBUG XDG_PathsLock_Debug =
+{
+ 0, 0, &XDG_PathsLock,
+ { &XDG_PathsLock_Debug.ProcessLocksList,
+ &XDG_PathsLock_Debug.ProcessLocksList},
+ 0, 0, { (DWORD_PTR)__FILE__ ": XDG_PathsLock"}
+};
+static CRITICAL_SECTION XDG_PathsLock = { &XDG_PathsLock_Debug, -1, 0, 0, 0, 0 };
+
+typedef struct
+{
+ const char *var_name;
+ const char *default_value;
+} std_path;
+
+static const std_path paths[] = {
+ {"XDG_DATA_HOME", "$HOME/.local/share"},
+ {"XDG_CONFIG_HOME", "$HOME/.config"},
+ {"XDG_DATA_DIRS", "/usr/local/share:/usr/share"},
+ {"XDG_CONFIG_DIRS", "/etc/xdg"},
+ {"XDG_CACHE_HOME", "$HOME/.cache"}
+};
+
+#define PATHS_COUNT (sizeof(paths)/sizeof(paths[0]))
+
+/* will be filled with paths as they are computed */
+static const char *path_values[PATHS_COUNT] = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static char *load_path(int path_id)
+{
+ char *env = getenv(paths[path_id].var_name);
+ char *ret;
+
+ if (env != NULL && env[0]=='/')
+ {
+ ret = SHAlloc(strlen(env)+1);
+ if (ret != NULL)
+ lstrcpyA(ret, env);
+ return ret;
+ }
+
+ if (memcmp(paths[path_id].default_value, "$HOME", 5)==0)
+ {
+ char *home = getenv("HOME");
+ int len;
+
+ if (!home) return NULL;
+ ret = SHAlloc(strlen(home)+strlen(paths[path_id].default_value)-5+1);
+ if (ret == NULL) return NULL;
+
+ lstrcpyA(ret, home);
+ len = strlen(ret);
+ if (len>0 && ret[len-1]=='/')
+ ret[--len]=0;
+ lstrcatA(ret, paths[path_id].default_value+5);
+ return ret;
+ }
+
+ ret = SHAlloc(strlen(paths[path_id].default_value)+1);
+ if (ret != NULL)
+ lstrcpyA(ret, env);
+ return ret;
+}
+
+/******************************************************************************
+ * XDG_GetPath [internal]
+ *
+ * Get one of the XDG standard patch. The return value shouldn't be modified nor
+ * freed. A return value of NULL means that the memory is exhausted or the input
+ * is invalid
+ *
+ * For XDG_DATA_HOME, XDG_CONFIG_HOME and XDG_CACHE_HOME the result is a Unix path.
+ * For XDG_DATA_DIRS and XDG_CONFIG_DIRS the result is a colon-separated list of Unix
+ * paths
+ *
+ * The paths are guaranteed to start with '/'
+ */
+const char *XDG_GetPath(int path_id)
+{
+ if (path_id >= PATHS_COUNT || path_id < 0)
+ {
+ ERR("Invalid path_id %d\n", path_id);
+ return NULL;
+ }
+
+ if (path_values[path_id] != NULL)
+ return path_values[path_id];
+ EnterCriticalSection(&XDG_PathsLock);
+ if (path_values[path_id] == NULL)
+ path_values[path_id] = load_path(path_id);
+ LeaveCriticalSection(&XDG_PathsLock);
+ return path_values[path_id];
+}
+
+/******************************************************************************
+ * XDG_GetPath [internal]
+ *
+ * Build a string with a subpath of one of the XDG standard paths.
+ * The root can be one of XDG_DATA_HOME, XDG_CONFIG_HOME and XDG_CACHE_HOME.
+ * The subpath is a path relative to that root (it shouldn't start with a slash)
+ *
+ * The returned path should be freed with SHFree. A return of NULL means that the
+ * memory is exhausted or the parameters are invalid
+ */
+char *XDG_BuildPath(int root_id, const char *subpath)
+{
+ const char *root_path = XDG_GetPath(root_id);
+ char *ret_buffer;
+ int root_len;
+
+ if (root_id == XDG_DATA_DIRS || root_id == XDG_CONFIG_DIRS)
+ {
+ ERR("Invalid path id %d\n", root_id);
+ return NULL;
+ }
+
+ if (root_path == NULL) return NULL;
+ root_len = strlen(root_path);
+ if (root_path[root_len-1]=='/') root_len--;
+ ret_buffer = SHAlloc(root_len+1+strlen(subpath)+1);
+ if (ret_buffer == NULL) return NULL;
+ lstrcpyA(ret_buffer, root_path);
+ ret_buffer[root_len]='/';
+ lstrcpyA(ret_buffer+root_len+1, subpath);
+ return ret_buffer;
+}
+
+/******************************************************************************
+ * XDG_MakeDirs [internal]
+ *
+ * Checks that all the directories on the specified path exists. If some don't exists
+ * they are created with mask 0700 as required by many the freedeskop.org specs.
+ * If the path doesn't end with '/' it is assumed to be a path to a file and the last
+ * segment is not checked
+ *
+ * In case of a failure the errno is always set and can be used e.g for debugging
+ *
+ * RETURNS
+ * TRUE on success, FALSE on error
+ */
+BOOL XDG_MakeDirs(const char *path)
+{
+ int last_slash = 0;
+ BOOL success = TRUE;
+ struct stat tmp;
+ char *buffer = SHAlloc(strlen(path)+1);
+
+ if (buffer == NULL)
+ {
+ errno = ENOMEM;
+ return FALSE;
+ }
+ lstrcpyA(buffer, path);
+
+ TRACE("(%s)\n", debugstr_a(path));
+ while (1)
+ {
+ char *slash=strchr(buffer+last_slash+1, '/');
+ if (slash==NULL)
+ break;
+
+ /* cut the string at that position and create the directory if it doesn't exist */
+ *slash=0;
+ TRACE("Checking path %s\n", debugstr_a(buffer));
+ success = (stat(buffer, &tmp)==0);
+ if (!success && errno==ENOENT)
+ {
+ TRACE("Creating\n");
+ success = (mkdir(buffer, 0700)==0);
+ }
+ if (!success)
+ {
+ WARN("Couldn't process directory %s (errno=%d)\n", debugstr_a(buffer), errno);
+ break;
+ }
+ *slash='/';
+ last_slash = slash-buffer;
+ }
+ SHFree(buffer);
+ return success;
+}
+
+/*
+ * .desktop files functions
+ */
+
+
+/******************************************************************************
+ * dskentry_encode [internal]
+ *
+ * Escape the characters that can't be present in a desktop entry value like \n, leading
+ * spaces etc. The output parameter may be NULL. Then only the number of characters will
+ * be computers.
+ *
+ * RETURNS
+ * The number of characters after escaping the special characters, including the
+ * terminating NUL.
+ */
+static int dskentry_encode(const char *value, char *output)
+{
+ int only_spc = TRUE;
+ int num_written = 0;
+ const char *c;
+ for (c = value; *c; c++)
+ {
+ if (only_spc && *c==' ')
+ {
+ if (output)
+ {
+ *(output++) = '\\';
+ *(output++) = 's';
+ }
+ num_written += 2;
+ continue;
+ }
+ only_spc = FALSE;
+
+ if (*c=='\t' || *c=='\r' || *c=='\n' || *c=='\\')
+ {
+ if (output)
+ {
+ *(output++) = '\\';
+ if (*c=='\t') *(output++) = 't';
+ if (*c=='\r') *(output++) = 'r';
+ if (*c=='\n') *(output++) = 'n';
+ if (*c=='\\') *(output++) = '\\';
+ }
+ num_written += 2;
+ }
+ else
+ {
+ if (output)
+ *(output++)=*c;
+ num_written++;
+ }
+ }
+
+ if (output)
+ *(output++) = 0;
+ num_written++;
+ return num_written;
+}
+
+
+/******************************************************************************
+ * url_encode [internal]
+ *
+ * URL-encode the given string (i.e. use escape codes like %20). Note that an
+ * URL-encoded string can be used as a value in desktop entry files as all
+ * unsafe characters are escaped.
+ *
+ * The output can be NULL. Then only the number of characters will be counted
+ *
+ * RETURNS
+ * The number of characters after escaping the special characters, including the
+ * terminating NUL.
+ */
+static int url_encode(const char *value, char *output)
+{
+ static const char *unsafechars = "^&`{}|[]'<>\\#%\"+";
+ static const char *hexchars = "0123456789ABCDEF";
+ int num_written = 0;
+ const char *c;
+
+ for (c = value; *c; c++)
+ {
+ if (*c<=0x20 || *c>=0x7f || strchr(unsafechars, *c))
+ {
+ if (output)
+ {
+ *(output++) = '%';
+ *(output++) = hexchars[(unsigned)(*c)/16];
+ *(output++) = hexchars[(unsigned)(*c)%16];
+ }
+ num_written += 3;
+ }
+ else
+ {
+ if (output)
+ *(output++) = *c;
+ num_written++;
+ }
+ }
+
+ if (output)
+ *(output++) = 0;
+ num_written++;
+
+ return num_written;
+}
+
+static int escape_value(const char *value, DWORD dwFlags, char *output)
+{
+ if (dwFlags & XDG_URLENCODE)
+ return url_encode(value, output);
+ return dskentry_encode(value, output);
+}
+
+/******************************************************************************
+ * XDG_WriteDesktopStringEntry [internal]
+ *
+ * Writes a key=value pair into the specified file descriptor.
+ *
+ * RETURNS
+ * TRUE on success, else FALSE
+ */
+BOOL XDG_WriteDesktopStringEntry(int writer, const char *keyName, DWORD dwFlags, const char *value)
+{
+ int keyLen = lstrlenA(keyName);
+ int valueLen = escape_value(value, dwFlags, NULL);
+ char *string = SHAlloc(keyLen+1+valueLen);
+ BOOL ret;
+
+ if (string == NULL)
+ return FALSE;
+ lstrcpyA(string, keyName);
+ string[keyLen] = '=';
+ escape_value(value, dwFlags, string+keyLen+1);
+ string[keyLen+1+valueLen-1]='\n'; /* -1 because valueLen contains the last NUL character */
+ ret = (write(writer, string, keyLen+1+valueLen)!=-1);
+ SHFree(string);
+ return ret;
+}
diff --git a/dlls/shell32/xdg.h b/dlls/shell32/xdg.h
new file mode 100644
index 0000000..46a8618
--- /dev/null
+++ b/dlls/shell32/xdg.h
@@ -0,0 +1,39 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+#ifndef __XDG_H__
+#define __XDG_H__
+
+/*
+ * XDG directories access
+ */
+#define XDG_DATA_HOME 0
+#define XDG_CONFIG_HOME 1
+#define XDG_DATA_DIRS 2
+#define XDG_CONFIG_DIRS 3
+#define XDG_CACHE_HOME 4
+
+const char *XDG_GetPath(int path_id);
+char *XDG_BuildPath(int root_id, const char *subpath);
+int XDG_MakeDirs(const char *path);
+
+#define XDG_URLENCODE 0x1
+BOOL XDG_WriteDesktopStringEntry(int fd, const char *keyName, DWORD dwFlags, const char *value);
+
+/* implemented in trash.c */
+BOOL TRASH_CanTrashFile(LPCWSTR wszPath);
+BOOL TRASH_TrashFile(LPCWSTR wszPath);
+
+#endif /* ndef __XDG_H__ */