]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilesystemwin32.c
Ignore empty volume labels; assume that GetVolumeInformation would fail if
[~andy/gtk] / gtk / gtkfilesystemwin32.c
1 /* GTK - The GIMP Toolkit
2  * gtkfilesystemwin32.c: Default implementation of GtkFileSystem for Windows
3  * Copyright (C) 2003, Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 #include <config.h>
22 #include "gtkfilesystem.h"
23 #include "gtkfilesystemwin32.h"
24 #include "gtkintl.h"
25 #include "gtkstock.h"
26 #include "gtkiconfactory.h"
27
28 #include <errno.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <ctype.h>
32 #include <sys/types.h>
33
34 #ifdef G_OS_WIN32
35 #define WIN32_LEAN_AND_MEAN
36 #include <windows.h>
37 #include <shellapi.h> /* ExtractAssociatedIcon */
38 #include <direct.h>
39 #include <io.h>
40 #define mkdir(p,m) _mkdir(p)
41 #include <gdk/win32/gdkwin32.h> /* gdk_win32_hdc_get */
42 #else
43 #error "The implementation is win32 only."
44 #endif /* G_OS_WIN32 */
45
46 typedef struct _GtkFileSystemWin32Class GtkFileSystemWin32Class;
47
48 #define GTK_FILE_SYSTEM_WIN32_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_SYSTEM_WIN32, GtkFileSystemWin32Class))
49 #define GTK_IS_FILE_SYSTEM_WIN32_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_SYSTEM_WIN32))
50 #define GTK_FILE_SYSTEM_WIN32_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_SYSTEM_WIN32, GtkFileSystemWin32Class))
51
52 struct _GtkFileSystemWin32Class
53 {
54   GObjectClass parent_class;
55 };
56
57 struct _GtkFileSystemWin32
58 {
59   GObject parent_instance;
60 };
61
62 #define GTK_TYPE_FILE_FOLDER_WIN32             (gtk_file_folder_win32_get_type ())
63 #define GTK_FILE_FOLDER_WIN32(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_FILE_FOLDER_WIN32, GtkFileFolderWin32))
64 #define GTK_IS_FILE_FOLDER_WIN32(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_FILE_FOLDER_WIN32))
65 #define GTK_FILE_FOLDER_WIN32_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_FOLDER_WIN32, GtkFileFolderWin32Class))
66 #define GTK_IS_FILE_FOLDER_WIN32_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_FOLDER_WIN32))
67 #define GTK_FILE_FOLDER_WIN32_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_FOLDER_WIN32, GtkFileFolderWin32Class))
68
69 typedef struct _GtkFileFolderWin32      GtkFileFolderWin32;
70 typedef struct _GtkFileFolderWin32Class GtkFileFolderWin32Class;
71
72 struct _GtkFileFolderWin32Class
73 {
74   GObjectClass parent_class;
75 };
76
77 struct _GtkFileFolderWin32
78 {
79   GObject parent_instance;
80
81   GtkFileInfoType types;
82   gchar *filename;
83 };
84
85 static GObjectClass *system_parent_class;
86 static GObjectClass *folder_parent_class;
87
88 static void           gtk_file_system_win32_class_init       (GtkFileSystemWin32Class  *class);
89 static void           gtk_file_system_win32_iface_init       (GtkFileSystemIface       *iface);
90 static void           gtk_file_system_win32_init             (GtkFileSystemWin32       *impl);
91 static void           gtk_file_system_win32_finalize         (GObject                  *object);
92
93 static GSList *       gtk_file_system_win32_list_volumes     (GtkFileSystem      *file_system);
94 static GtkFileSystemVolume *gtk_file_system_win32_get_volume_for_path (GtkFileSystem     *file_system,
95                                                                        const GtkFilePath *path);
96
97 static GtkFileFolder *gtk_file_system_win32_get_folder       (GtkFileSystem            *file_system,
98                                                               const GtkFilePath        *path,
99                                                               GtkFileInfoType           types,
100                                                               GError                  **error);
101 static gboolean       gtk_file_system_win32_create_folder    (GtkFileSystem            *file_system,
102                                                               const GtkFilePath        *path,
103                                                               GError                  **error);
104
105 static void         gtk_file_system_win32_volume_free             (GtkFileSystem       *file_system,
106                                                                    GtkFileSystemVolume *volume);
107 static GtkFilePath *gtk_file_system_win32_volume_get_base_path    (GtkFileSystem       *file_system,
108                                                                    GtkFileSystemVolume *volume);
109 static gboolean     gtk_file_system_win32_volume_get_is_mounted   (GtkFileSystem       *file_system,
110                                                                    GtkFileSystemVolume *volume);
111 static gboolean     gtk_file_system_win32_volume_mount            (GtkFileSystem       *file_system,
112                                                                    GtkFileSystemVolume *volume,
113                                                                    GError             **error);
114 static gchar *      gtk_file_system_win32_volume_get_display_name (GtkFileSystem       *file_system,
115                                                                    GtkFileSystemVolume *volume);
116 static GdkPixbuf *  gtk_file_system_win32_volume_render_icon      (GtkFileSystem        *file_system,
117                                                                    GtkFileSystemVolume  *volume,
118                                                                    GtkWidget            *widget,
119                                                                    gint                  pixel_size,
120                                                                    GError              **error);
121
122 static gboolean       gtk_file_system_win32_get_parent       (GtkFileSystem            *file_system,
123                                                               const GtkFilePath        *path,
124                                                               GtkFilePath             **parent,
125                                                               GError                  **error);
126 static GtkFilePath *  gtk_file_system_win32_make_path        (GtkFileSystem            *file_system,
127                                                               const GtkFilePath        *base_path,
128                                                               const gchar              *display_name,
129                                                               GError                  **error);
130 static gboolean       gtk_file_system_win32_parse            (GtkFileSystem            *file_system,
131                                                               const GtkFilePath        *base_path,
132                                                               const gchar              *str,
133                                                               GtkFilePath             **folder,
134                                                               gchar                   **file_part,
135                                                               GError                  **error);
136 static gchar *        gtk_file_system_win32_path_to_uri      (GtkFileSystem            *file_system,
137                                                               const GtkFilePath        *path);
138 static gchar *        gtk_file_system_win32_path_to_filename (GtkFileSystem            *file_system,
139                                                               const GtkFilePath        *path);
140 static GtkFilePath *  gtk_file_system_win32_uri_to_path      (GtkFileSystem            *file_system,
141                                                               const gchar              *uri);
142 static GtkFilePath *  gtk_file_system_win32_filename_to_path (GtkFileSystem            *file_system,
143                                                               const gchar              *filename);
144 static GdkPixbuf *gtk_file_system_win32_render_icon (GtkFileSystem     *file_system,
145                                                      const GtkFilePath *path,
146                                                      GtkWidget         *widget,
147                                                      gint               pixel_size,
148                                                      GError           **error);
149
150 static gboolean       gtk_file_system_win32_insert_bookmark  (GtkFileSystem            *file_system,
151                                                               const GtkFilePath        *path,
152                                                               gint               position,
153                                                               GError                  **error);
154 static gboolean       gtk_file_system_win32_remove_bookmark  (GtkFileSystem            *file_system,
155                                                               const GtkFilePath        *path,
156                                                               GError                  **error);
157 static GSList *       gtk_file_system_win32_list_bookmarks   (GtkFileSystem            *file_system);
158 static GType          gtk_file_folder_win32_get_type         (void);
159 static void           gtk_file_folder_win32_class_init       (GtkFileFolderWin32Class  *class);
160 static void           gtk_file_folder_win32_iface_init       (GtkFileFolderIface       *iface);
161 static void           gtk_file_folder_win32_init             (GtkFileFolderWin32       *impl);
162 static void           gtk_file_folder_win32_finalize         (GObject                  *object);
163 static GtkFileInfo *  gtk_file_folder_win32_get_info         (GtkFileFolder            *folder,
164                                                               const GtkFilePath        *path,
165                                                               GError                  **error);
166 static gboolean       gtk_file_folder_win32_list_children    (GtkFileFolder            *folder,
167                                                               GSList                  **children,
168                                                               GError                  **error);
169
170 static gchar *        filename_from_path                     (const GtkFilePath        *path);
171 static GtkFilePath *  filename_to_path                       (const gchar              *filename);
172
173 static gboolean       filename_is_root                       (const char               *filename);
174 static GtkFileInfo *  filename_get_info                      (const gchar              *filename,
175                                                               GtkFileInfoType           types,
176                                                               GError                  **error);
177
178 /* some info kept together for volumes */
179 struct _GtkFileSystemVolume
180 {
181   gchar    *drive;
182   gboolean  is_mounted;
183 };
184
185 /*
186  * GtkFileSystemWin32
187  */
188 GType
189 gtk_file_system_win32_get_type (void)
190 {
191   static GType file_system_win32_type = 0;
192
193   if (!file_system_win32_type)
194     {
195       static const GTypeInfo file_system_win32_info =
196       {
197         sizeof (GtkFileSystemWin32Class),
198         NULL,           /* base_init */
199         NULL,           /* base_finalize */
200         (GClassInitFunc) gtk_file_system_win32_class_init,
201         NULL,           /* class_finalize */
202         NULL,           /* class_data */
203         sizeof (GtkFileSystemWin32),
204         0,              /* n_preallocs */
205         (GInstanceInitFunc) gtk_file_system_win32_init,
206       };
207       
208       static const GInterfaceInfo file_system_info =
209       {
210         (GInterfaceInitFunc) gtk_file_system_win32_iface_init, /* interface_init */
211         NULL,                                                  /* interface_finalize */
212         NULL                                                   /* interface_data */
213       };
214
215       file_system_win32_type = g_type_register_static (G_TYPE_OBJECT,
216                                                        "GtkFileSystemWin32",
217                                                        &file_system_win32_info, 0);
218       g_type_add_interface_static (file_system_win32_type,
219                                    GTK_TYPE_FILE_SYSTEM,
220                                    &file_system_info);
221     }
222
223   return file_system_win32_type;
224 }
225
226 /**
227  * gtk_file_system_win32_new:
228  * 
229  * Creates a new #GtkFileSystemWin32 object. #GtkFileSystemWin32
230  * implements the #GtkFileSystem interface with direct access to
231  * the filesystem using Windows API calls
232  * 
233  * Return value: the new #GtkFileSystemWin32 object
234  **/
235 GtkFileSystem *
236 gtk_file_system_win32_new (void)
237 {
238   return g_object_new (GTK_TYPE_FILE_SYSTEM_WIN32, NULL);
239 }
240
241 static void
242 gtk_file_system_win32_class_init (GtkFileSystemWin32Class *class)
243 {
244   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
245
246   system_parent_class = g_type_class_peek_parent (class);
247   
248   gobject_class->finalize = gtk_file_system_win32_finalize;
249 }
250
251 static void
252 gtk_file_system_win32_iface_init (GtkFileSystemIface *iface)
253 {
254   iface->list_volumes = gtk_file_system_win32_list_volumes;
255   iface->get_volume_for_path = gtk_file_system_win32_get_volume_for_path;
256   iface->get_folder = gtk_file_system_win32_get_folder;
257   iface->create_folder = gtk_file_system_win32_create_folder;
258   iface->volume_free = gtk_file_system_win32_volume_free;
259   iface->volume_get_base_path = gtk_file_system_win32_volume_get_base_path;
260   iface->volume_get_is_mounted = gtk_file_system_win32_volume_get_is_mounted;
261   iface->volume_mount = gtk_file_system_win32_volume_mount;
262   iface->volume_get_display_name = gtk_file_system_win32_volume_get_display_name;
263   iface->volume_render_icon = gtk_file_system_win32_volume_render_icon;
264   iface->get_parent = gtk_file_system_win32_get_parent;
265   iface->make_path = gtk_file_system_win32_make_path;
266   iface->parse = gtk_file_system_win32_parse;
267   iface->path_to_uri = gtk_file_system_win32_path_to_uri;
268   iface->path_to_filename = gtk_file_system_win32_path_to_filename;
269   iface->uri_to_path = gtk_file_system_win32_uri_to_path;
270   iface->filename_to_path = gtk_file_system_win32_filename_to_path;
271   iface->render_icon = gtk_file_system_win32_render_icon;
272   iface->insert_bookmark = gtk_file_system_win32_insert_bookmark;
273   iface->remove_bookmark = gtk_file_system_win32_remove_bookmark;
274   iface->list_bookmarks = gtk_file_system_win32_list_bookmarks;
275 }
276
277 static void
278 gtk_file_system_win32_init (GtkFileSystemWin32 *system_win32)
279 {
280 }
281
282 static void
283 gtk_file_system_win32_finalize (GObject *object)
284 {
285   system_parent_class->finalize (object);
286 }
287
288 static GSList *
289 gtk_file_system_win32_list_volumes (GtkFileSystem *file_system)
290 {
291   DWORD   drives;
292   gchar   drive[4] = "A:\\";
293   GSList *list = NULL;
294
295   drives = GetLogicalDrives();
296
297   if (!drives)
298     g_warning ("GetLogicalDrives failed.");
299
300   while (drives && drive[0] <= 'Z')
301     {
302       if (drives & 1)
303       {
304         GtkFileSystemVolume *vol = g_new0 (GtkFileSystemVolume, 1);
305         if (drive[0] == 'A' || drive[0] == 'B')
306           vol->is_mounted = FALSE; /* skip floppy */
307         else
308           vol->is_mounted = TRUE; /* handle other removable drives special, too? */
309
310         vol->drive = g_strdup (drive);
311         list = g_slist_append (list, vol);
312       }
313       drives >>= 1;
314       drive[0]++;
315     }
316   return list;
317 }
318
319 static GtkFileSystemVolume *
320 gtk_file_system_win32_get_volume_for_path (GtkFileSystem     *file_system,
321                                            const GtkFilePath *path)
322 {
323   GtkFileSystemVolume *vol = g_new0 (GtkFileSystemVolume, 1);
324   gchar* p = g_strndup (gtk_file_path_get_string (path), 3);
325
326   g_return_val_if_fail (p != NULL, NULL);
327
328   /*FIXME: gtk_file_path_compare() is case sensitive, we are not*/
329   p[0] = g_ascii_toupper (p[0]);
330   vol->drive = p;
331   vol->is_mounted = (p[0] != 'A' && p[0] != 'B');
332
333   return vol;
334 }
335
336 static GtkFileFolder *
337 gtk_file_system_win32_get_folder (GtkFileSystem    *file_system,
338                                  const GtkFilePath *path,
339                                  GtkFileInfoType    types,
340                                  GError           **error)
341 {
342   GtkFileFolderWin32 *folder_win32;
343   gchar *filename;
344
345   filename = filename_from_path (path);
346   g_return_val_if_fail (filename != NULL, NULL);
347
348   folder_win32 = g_object_new (GTK_TYPE_FILE_FOLDER_WIN32, NULL);
349   folder_win32->filename = filename;
350   folder_win32->types = types;
351
352   return GTK_FILE_FOLDER (folder_win32);
353 }
354
355 static gboolean
356 gtk_file_system_win32_create_folder (GtkFileSystem     *file_system,
357                                      const GtkFilePath *path,
358                                      GError           **error)
359 {
360   gchar *filename;
361   gboolean result;
362
363   filename = filename_from_path (path);
364   g_return_val_if_fail (filename != NULL, FALSE);
365   
366   result = mkdir (filename, 0777) != 0;
367   
368   if (!result)
369     {
370       gchar *filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
371       g_set_error (error,
372                    GTK_FILE_SYSTEM_ERROR,
373                    GTK_FILE_SYSTEM_ERROR_NONEXISTENT,
374                    _("error creating directory '%s': %s"),
375                    filename_utf8 ? filename_utf8 : "???",
376                    g_strerror (errno));
377       g_free (filename_utf8);
378     }
379   
380   g_free (filename);
381   
382   return result;
383 }
384
385 static void
386 gtk_file_system_win32_volume_free (GtkFileSystem        *file_system,
387                                    GtkFileSystemVolume  *volume)
388 {
389   g_free (volume->drive);
390   g_free (volume);
391 }
392
393 static GtkFilePath *
394 gtk_file_system_win32_volume_get_base_path (GtkFileSystem        *file_system,
395                                             GtkFileSystemVolume  *volume)
396 {
397   return (GtkFilePath *) g_strdup (volume->drive);
398 }
399
400 static gboolean
401 gtk_file_system_win32_volume_get_is_mounted (GtkFileSystem        *file_system,
402                                              GtkFileSystemVolume  *volume)
403 {
404   return volume->is_mounted;
405 }
406
407 static gboolean
408 gtk_file_system_win32_volume_mount (GtkFileSystem        *file_system, 
409                                     GtkFileSystemVolume  *volume,
410                                     GError              **error)
411 {
412   g_set_error (error,
413                GTK_FILE_SYSTEM_ERROR,
414                GTK_FILE_SYSTEM_ERROR_FAILED,
415                _("This file system does not support mounting"));
416   return FALSE;
417 }
418
419 static gchar *
420 gtk_file_system_win32_volume_get_display_name (GtkFileSystem       *file_system,
421                                               GtkFileSystemVolume *volume)
422 {
423   gchar *real_display_name;
424   gunichar2 *wdrive = g_utf8_to_utf16 (volume->drive, -1, NULL, NULL, NULL);
425   gunichar2  wname[80];
426
427   g_return_val_if_fail (wdrive != NULL, NULL);
428
429   if (GetVolumeInformationW (wdrive,
430                             wname, G_N_ELEMENTS(wname), 
431                             NULL, /* serial number */
432                             NULL, /* max. component length */
433                             NULL, /* fs flags */
434                             NULL, 0) /* fs type like FAT, NTFS */
435                             && wname[0])
436     {
437       gchar *name = g_utf16_to_utf8 (wname, -1, NULL, NULL, NULL);
438       real_display_name = g_strconcat (name, " (", volume->drive, ")", NULL);
439       g_free (name);
440     }
441   else
442     real_display_name = g_strdup (volume->drive);
443
444   g_free (wdrive);
445
446   return real_display_name;
447 }
448
449 static GdkPixbuf *
450 gtk_file_system_win32_volume_render_icon (GtkFileSystem        *file_system,
451                                          GtkFileSystemVolume  *volume,
452                                          GtkWidget            *widget,
453                                          gint                  pixel_size,
454                                          GError              **error)
455 {
456   GtkIconSet *icon_set = NULL;
457   DWORD dt = GetDriveType (volume->drive);
458
459   switch (dt)
460     {
461     case DRIVE_REMOVABLE :
462       icon_set = gtk_style_lookup_icon_set (widget->style, GTK_STOCK_FLOPPY);
463       break;
464     case DRIVE_CDROM :
465       icon_set = gtk_style_lookup_icon_set (widget->style, GTK_STOCK_CDROM);
466       break;
467     case DRIVE_REMOTE :
468       icon_set = gtk_style_lookup_icon_set (widget->style, GTK_STOCK_NETWORK);
469       break;
470     case DRIVE_FIXED :
471       icon_set = gtk_style_lookup_icon_set (widget->style, GTK_STOCK_HARDDISK);
472       break;
473     case DRIVE_RAMDISK :
474       /*FIXME: need a ram stock icon
475       gtk_file_info_set_icon_type (info, GTK_STOCK_OPEN);*/
476       break;
477     default :
478       g_assert_not_reached ();
479     }
480
481   return gtk_icon_set_render_icon (icon_set, 
482                                    widget->style,
483                                    gtk_widget_get_direction (widget),
484                                    GTK_STATE_NORMAL,
485                                    GTK_ICON_SIZE_BUTTON,
486                                    widget, NULL); 
487 }
488
489 static gboolean
490 gtk_file_system_win32_get_parent (GtkFileSystem     *file_system,
491                                   const GtkFilePath *path,
492                                   GtkFilePath      **parent,
493                                   GError           **error)
494 {
495   gchar *filename = filename_from_path (path);
496   g_return_val_if_fail (filename != NULL, FALSE);
497
498   if (filename_is_root (filename))
499     {
500       *parent = NULL;
501     }
502   else
503     {
504       gchar *parent_filename = g_path_get_dirname (filename);
505       *parent = filename_to_path (parent_filename);
506       g_free (parent_filename);
507     }
508
509   g_free (filename);
510
511   return TRUE;
512 }
513
514 static GtkFilePath *
515 gtk_file_system_win32_make_path (GtkFileSystem     *file_system,
516                                  const GtkFilePath *base_path,
517                                  const gchar       *display_name,
518                                  GError           **error)
519 {
520   const char *base_filename;
521   gchar *filename;
522   gchar *full_filename;
523   GError *tmp_error = NULL;
524   GtkFilePath *result;
525   
526   base_filename = gtk_file_path_get_string (base_path);
527   g_return_val_if_fail (base_filename != NULL, NULL);
528   g_return_val_if_fail (g_path_is_absolute (base_filename), NULL);
529
530   filename = g_filename_from_utf8 (display_name, -1, NULL, NULL, &tmp_error);
531   if (!filename)
532     {
533       g_set_error (error,
534                    GTK_FILE_SYSTEM_ERROR,
535                    GTK_FILE_SYSTEM_ERROR_BAD_FILENAME,
536                    "%s",
537                    tmp_error->message);
538       
539       g_error_free (tmp_error);
540
541       return NULL;
542     }
543     
544   full_filename = g_build_filename (base_filename, filename, NULL);
545   result = filename_to_path (full_filename);
546   g_free (filename);
547   g_free (full_filename);
548   
549   return result;
550 }
551
552 /* If this was a publically exported function, it should return
553  * a dup'ed result, but we make it modify-in-place for efficiency
554  * here, and because it works for us.
555  */
556 static void
557 canonicalize_filename (gchar *filename)
558 {
559   gchar *p, *q;
560   gboolean last_was_slash = FALSE;
561
562   p = filename;
563   q = filename;
564
565   while (*p)
566     {
567       if (*p == G_DIR_SEPARATOR)
568         {
569           if (!last_was_slash)
570             *q++ = G_DIR_SEPARATOR;
571
572           last_was_slash = TRUE;
573         }
574       else
575         {
576           if (last_was_slash && *p == '.')
577             {
578               if (*(p + 1) == G_DIR_SEPARATOR ||
579                   *(p + 1) == '\0')
580                 {
581                   if (*(p + 1) == '\0')
582                     break;
583                   
584                   p += 1;
585                 }
586               else if (*(p + 1) == '.' &&
587                        (*(p + 2) == G_DIR_SEPARATOR ||
588                         *(p + 2) == '\0'))
589                 {
590                   if (q > filename + 1)
591                     {
592                       q--;
593                       while (q > filename + 1 &&
594                              *(q - 1) != G_DIR_SEPARATOR)
595                         q--;
596                     }
597
598                   if (*(p + 2) == '\0')
599                     break;
600                   
601                   p += 2;
602                 }
603               else
604                 {
605                   *q++ = *p;
606                   last_was_slash = FALSE;
607                 }
608             }
609           else
610             {
611               *q++ = *p;
612               last_was_slash = FALSE;
613             }
614         }
615
616       p++;
617     }
618
619   if (q > filename + 1 && *(q - 1) == G_DIR_SEPARATOR)
620     q--;
621
622   *q = '\0';
623 }
624
625 static gboolean
626 gtk_file_system_win32_parse (GtkFileSystem     *file_system,
627                              const GtkFilePath *base_path,
628                              const gchar       *str,
629                              GtkFilePath      **folder,
630                              gchar            **file_part,
631                              GError           **error)
632 {
633   const char *base_filename;
634   gchar *last_slash;
635   gboolean result = FALSE;
636
637   base_filename = gtk_file_path_get_string (base_path);
638   g_return_val_if_fail (base_filename != NULL, FALSE);
639   g_return_val_if_fail (g_path_is_absolute (base_filename), FALSE);
640   
641   last_slash = strrchr (str, G_DIR_SEPARATOR);
642   if (!last_slash)
643     {
644       *folder = gtk_file_path_copy (base_path);
645       *file_part = g_strdup (str);
646       result = TRUE;
647     }
648   else
649     {
650       gchar *folder_part;
651       gchar *folder_path;
652       GError *tmp_error = NULL;
653
654       if (last_slash == str)
655         folder_part = g_strdup ("/");
656       else
657         folder_part = g_filename_from_utf8 (str, last_slash - str,
658                                             NULL, NULL, &tmp_error);
659
660       if (!folder_part)
661         {
662           g_set_error (error,
663                        GTK_FILE_SYSTEM_ERROR,
664                        GTK_FILE_SYSTEM_ERROR_BAD_FILENAME,
665                        "%s",
666                        tmp_error->message);
667           g_error_free (tmp_error);
668         }
669       else
670         {
671           if (folder_part[1] == ':')
672             folder_path = folder_part;
673           else
674             {
675               folder_path = g_build_filename (base_filename, folder_part, NULL);
676               g_free (folder_part);
677             }
678
679           canonicalize_filename (folder_path);
680           
681           *folder = filename_to_path (folder_path);
682           *file_part = g_strdup (last_slash + 1);
683
684           g_free (folder_path);
685
686           result = TRUE;
687         }
688     }
689
690   return result;
691 }
692
693 static gchar *
694 gtk_file_system_win32_path_to_uri (GtkFileSystem     *file_system,
695                                    const GtkFilePath *path)
696 {
697   return g_filename_to_uri (gtk_file_path_get_string (path), NULL, NULL);
698 }
699
700 static gchar *
701 gtk_file_system_win32_path_to_filename (GtkFileSystem     *file_system,
702                                         const GtkFilePath *path)
703 {
704   return g_strdup (gtk_file_path_get_string (path));
705 }
706
707 static GtkFilePath *
708 gtk_file_system_win32_uri_to_path (GtkFileSystem     *file_system,
709                                    const gchar       *uri)
710 {
711   gchar *filename = g_filename_from_uri (uri, NULL, NULL);
712   if (filename)
713     return gtk_file_path_new_steal (filename);
714   else
715     return NULL;
716 }
717
718 static GtkFilePath *
719 gtk_file_system_win32_filename_to_path (GtkFileSystem *file_system,
720                                         const gchar   *filename)
721 {
722   return gtk_file_path_new_dup (filename);
723 }
724
725 static gboolean
726 bookmarks_serialize (GSList  **bookmarks,
727                      gchar    *uri,
728                      gboolean  add,
729                      gint      position,
730                      GError  **error)
731 {
732   gchar   *filename;
733   gboolean ok = TRUE;
734   GSList  *list = *bookmarks;
735
736   filename = g_build_filename (g_get_home_dir (), ".gtk-bookmarks", NULL);
737
738   if (filename)
739     {
740       gchar *contents = NULL;
741       gsize  len = 0;
742       GSList *entry;
743       FILE  *f;   
744        
745       if (g_file_test (filename, G_FILE_TEST_EXISTS))
746         {
747           if (g_file_get_contents (filename, &contents, &len, error))
748             {
749               gchar **lines = g_strsplit (contents, "\n", -1);
750               gint    i;
751
752               for (i = 0; lines[i] != NULL; i++)
753                 {
754                   if (lines[i][0] && !g_slist_find_custom (list, lines[i], (GCompareFunc) strcmp))
755                     list = g_slist_append (list, g_strdup (lines[i]));
756                 }
757               g_strfreev (lines);
758             }
759           else
760             ok = FALSE;
761         }
762       if (ok && (f = fopen (filename, "wb")) != NULL)
763         {
764           entry = g_slist_find_custom (list, uri, (GCompareFunc) strcmp);
765           if (add)
766             {
767               /* g_slist_insert() and our insert semantics are 
768                * compatible, but maybe we should check for 
769                * positon > length ?
770                * 
771                */
772               if (!entry)
773                 list = g_slist_insert (list, g_strdup (uri), position);
774               else
775                 {
776                   g_set_error (error,
777                                GTK_FILE_SYSTEM_ERROR,
778                                GTK_FILE_SYSTEM_ERROR_ALREADY_EXISTS,
779                                "%s already exists in the bookmarks list",
780                                uri);
781                   ok = FALSE;
782                 }
783             }
784           else
785             {
786               /* to remove the given uri */
787               if (entry)
788                 list = g_slist_delete_link(list, entry);
789               for (entry = list; entry != NULL; entry = entry->next)
790                 {
791                   fputs (entry->data, f);
792                   fputs ("\n", f);
793                 }
794             }
795           fclose (f);
796         }
797       else if (ok && error)
798         {
799           g_set_error (error,
800                        GTK_FILE_SYSTEM_ERROR,
801                        GTK_FILE_SYSTEM_ERROR_FAILED,
802                        _("Bookmark saving failed (%s)"),
803                        g_strerror (errno));
804         }
805     }
806   *bookmarks = list;
807   return ok;
808 }
809
810 static GdkPixbuf*
811 extract_icon (const char* filename)
812 {
813   GdkPixbuf *pixbuf = NULL;
814   WORD iicon;
815   HICON hicon;
816   
817   if (!filename || !filename[0])
818     return NULL;
819
820   hicon = ExtractAssociatedIcon (GetModuleHandle (NULL), filename, &iicon);
821   if (hicon > (HICON)1)
822     {
823       ICONINFO ii;
824
825       if (GetIconInfo (hicon, &ii))
826         {
827           SIZE   size;
828           GdkPixmap *pixmap;
829           GdkGC *gc;
830           HDC    hdc;
831
832           if (!GetBitmapDimensionEx (ii.hbmColor, &size))
833             g_warning ("GetBitmapDimensionEx failed.");
834
835           if (size.cx < 1) size.cx = 32;
836           if (size.cy < 1) size.cy = 32;
837             
838           pixmap = gdk_pixmap_new (NULL, size.cx, size.cy, 
839                                    gdk_screen_get_system_visual (gdk_screen_get_default ())->depth);
840           gc = gdk_gc_new (pixmap);
841           hdc = gdk_win32_hdc_get (GDK_DRAWABLE (pixmap), gc, 0);
842
843           if (!DrawIcon (hdc, 0, 0, hicon))
844             g_warning ("DrawIcon failed");
845
846           gdk_win32_hdc_release (GDK_DRAWABLE (pixmap), gc, 0);
847
848           pixbuf = gdk_pixbuf_get_from_drawable (
849                      NULL, pixmap, 
850                      gdk_screen_get_system_colormap (gdk_screen_get_default ()),
851                      0, 0, 0, 0, size.cx, size.cy);
852           g_object_unref (pixmap);
853           g_object_unref (gc);
854         }
855       else
856         g_print ("GetIconInfo failed: %s\n", g_win32_error_message (GetLastError ())); 
857
858       if (!DestroyIcon (hicon))
859         g_warning ("DestroyIcon failed");
860     }
861   else
862     g_print ("ExtractAssociatedIcon failed: %s\n", g_win32_error_message (GetLastError ()));
863
864   return pixbuf;
865 }
866
867 static GtkIconSet *
868 win32_pseudo_mime_lookup (const char* name)
869 {
870   static GHashTable *mime_hash = NULL;
871   GtkIconSet *is = NULL;
872   char *p = strrchr(name, '.');
873   char *extension = p ? g_ascii_strdown (p, -1) : g_strdup ("");
874
875   if (!mime_hash)
876     mime_hash = g_hash_table_new (g_str_hash, g_str_equal);
877
878   /* do we already have it ? */
879   is = g_hash_table_lookup (mime_hash, extension);
880   if (is)
881     {
882       g_free (extension);
883       return is;
884     }
885   /* create icon and set */
886   {
887     GdkPixbuf *pixbuf = extract_icon (name);
888     if (pixbuf)
889       {
890         GtkIconSource* source = gtk_icon_source_new ();
891
892         is = gtk_icon_set_new_from_pixbuf (pixbuf);
893         gtk_icon_source_set_pixbuf (source, pixbuf);
894         gtk_icon_set_add_source (is, source);
895
896         gtk_icon_source_free (source);
897       }
898
899     g_hash_table_insert (mime_hash, extension, is);
900     return is;
901   }
902 }
903
904 static GdkPixbuf *
905 gtk_file_system_win32_render_icon (GtkFileSystem     *file_system,
906                                    const GtkFilePath *path,
907                                    GtkWidget         *widget,
908                                    gint               pixel_size,
909                                    GError           **error)
910 {
911   GtkIconSet *icon_set = NULL;
912   const char* filename = gtk_file_path_get_string (path);
913
914   /* handle drives with stock icons */
915   if (filename_is_root (filename))
916     {
917       gchar *filename2 = g_strconcat(filename, "\\", NULL);
918       DWORD dt = GetDriveType (filename2);
919
920       switch (dt)
921         {
922         case DRIVE_REMOVABLE :
923           icon_set = gtk_style_lookup_icon_set (widget->style, GTK_STOCK_FLOPPY);
924           break;
925         case DRIVE_CDROM :
926           icon_set = gtk_style_lookup_icon_set (widget->style, GTK_STOCK_CDROM);
927           break;
928         case DRIVE_FIXED : /* need a hard disk icon */
929           icon_set = gtk_style_lookup_icon_set (widget->style, GTK_STOCK_HARDDISK);
930           break;
931         default :
932           break;
933         }
934       g_free (filename2);
935     }
936   else if (g_file_test (filename, G_FILE_TEST_IS_DIR))
937     {
938       if (0 == strcmp (g_get_home_dir(), filename))
939         icon_set = gtk_style_lookup_icon_set (widget->style, GTK_STOCK_HOME);
940       else
941         icon_set = gtk_style_lookup_icon_set (widget->style, GTK_STOCK_OPEN);
942     }
943   else if (g_file_test (filename, G_FILE_TEST_IS_EXECUTABLE))
944     {
945       /* don't lookup all executable icons */
946       icon_set = gtk_style_lookup_icon_set (widget->style, GTK_STOCK_EXECUTE);
947     }
948   else if (g_file_test (filename, G_FILE_TEST_EXISTS))
949     {
950       icon_set = win32_pseudo_mime_lookup (filename);
951     }
952
953   if (!icon_set)
954     {
955        g_set_error (error,
956                   GTK_FILE_SYSTEM_ERROR,
957                   GTK_FILE_SYSTEM_ERROR_FAILED,
958                   _("This file system does not support icons for everything"));
959        return NULL;
960     }
961
962   // FIXME : I'd like to get from pixel_size (=20) back to
963   // icon size, which is an index, but there appears to be no way ?
964   return gtk_icon_set_render_icon (icon_set, 
965                                    widget->style,
966                                    gtk_widget_get_direction (widget),
967                                    GTK_STATE_NORMAL,
968                                    GTK_ICON_SIZE_BUTTON,
969                                    widget, NULL); 
970 }
971
972 static GSList *_bookmarks = NULL;
973
974 static gboolean
975 gtk_file_system_win32_insert_bookmark (GtkFileSystem     *file_system,
976                                     const GtkFilePath *path,
977                                     gint               position,
978                                     GError           **error)
979 {
980   gchar *uri = gtk_file_system_win32_path_to_uri (file_system, path);
981   gboolean ret = bookmarks_serialize (&_bookmarks, uri, TRUE, position, error);
982   if (ret)
983     g_signal_emit_by_name (file_system, "bookmarks-changed", 0);
984   g_free (uri);
985   return ret;
986                                
987 }
988
989 static gboolean
990 gtk_file_system_win32_remove_bookmark (GtkFileSystem     *file_system,
991                                        const GtkFilePath *path,
992                                        GError           **error)
993 {
994   gchar *uri = gtk_file_system_win32_path_to_uri (file_system, path);
995   gboolean ret = bookmarks_serialize (&_bookmarks, uri, FALSE, 0, error);
996   if (ret)
997     g_signal_emit_by_name (file_system, "bookmarks-changed", 0);
998   g_free (uri);
999   return ret;
1000 }
1001
1002 static GSList *
1003 gtk_file_system_win32_list_bookmarks (GtkFileSystem *file_system)
1004 {
1005   GSList *list = NULL;
1006   GSList *entry;
1007
1008
1009   if (bookmarks_serialize (&_bookmarks, "", FALSE, 0, NULL))
1010     {
1011       for (entry = _bookmarks; entry != NULL; entry = entry->next)
1012         {
1013           GtkFilePath *path = gtk_file_system_win32_uri_to_path (
1014                                 file_system, (gchar *)entry->data);
1015
1016           list = g_slist_append (list, path);
1017         }
1018     }
1019
1020   return list;
1021 }
1022
1023 /*
1024  * GtkFileFolderWin32
1025  */
1026 static GType
1027 gtk_file_folder_win32_get_type (void)
1028 {
1029   static GType file_folder_win32_type = 0;
1030
1031   if (!file_folder_win32_type)
1032     {
1033       static const GTypeInfo file_folder_win32_info =
1034       {
1035         sizeof (GtkFileFolderWin32Class),
1036         NULL,           /* base_init */
1037         NULL,           /* base_finalize */
1038         (GClassInitFunc) gtk_file_folder_win32_class_init,
1039         NULL,           /* class_finalize */
1040         NULL,           /* class_data */
1041         sizeof (GtkFileFolderWin32),
1042         0,              /* n_preallocs */
1043         (GInstanceInitFunc) gtk_file_folder_win32_init,
1044       };
1045       
1046       static const GInterfaceInfo file_folder_info =
1047       {
1048         (GInterfaceInitFunc) gtk_file_folder_win32_iface_init, /* interface_init */
1049         NULL,                                                 /* interface_finalize */
1050         NULL                                                  /* interface_data */
1051       };
1052
1053       file_folder_win32_type = g_type_register_static (G_TYPE_OBJECT,
1054                                                       "GtkFileFolderWin32",
1055                                                       &file_folder_win32_info, 0);
1056       g_type_add_interface_static (file_folder_win32_type,
1057                                    GTK_TYPE_FILE_FOLDER,
1058                                    &file_folder_info);
1059     }
1060
1061   return file_folder_win32_type;
1062 }
1063
1064 static void
1065 gtk_file_folder_win32_class_init (GtkFileFolderWin32Class *class)
1066 {
1067   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
1068
1069   folder_parent_class = g_type_class_peek_parent (class);
1070
1071   gobject_class->finalize = gtk_file_folder_win32_finalize;
1072 }
1073
1074 static void
1075 gtk_file_folder_win32_iface_init (GtkFileFolderIface *iface)
1076 {
1077   iface->get_info = gtk_file_folder_win32_get_info;
1078   iface->list_children = gtk_file_folder_win32_list_children;
1079 }
1080
1081 static void
1082 gtk_file_folder_win32_init (GtkFileFolderWin32 *impl)
1083 {
1084 }
1085
1086 static void
1087 gtk_file_folder_win32_finalize (GObject *object)
1088 {
1089   GtkFileFolderWin32 *folder_win32 = GTK_FILE_FOLDER_WIN32 (object);
1090
1091   g_free (folder_win32->filename);
1092   
1093   folder_parent_class->finalize (object);
1094 }
1095
1096 static GtkFileInfo *
1097 gtk_file_folder_win32_get_info (GtkFileFolder     *folder,
1098                                 const GtkFilePath *path,
1099                                 GError           **error)
1100 {
1101   GtkFileFolderWin32 *folder_win32 = GTK_FILE_FOLDER_WIN32 (folder);
1102   GtkFileInfo *info;
1103   gchar *dirname;
1104   gchar *filename;
1105   
1106   if (!path)
1107     {
1108       g_return_val_if_fail (filename_is_root (folder_win32->filename), NULL);
1109
1110       /* ??? */
1111       info = filename_get_info (folder_win32->filename, folder_win32->types, error);
1112
1113       return info;
1114     }
1115
1116   filename = filename_from_path (path);
1117   g_return_val_if_fail (filename != NULL, NULL);
1118
1119 #if 0
1120   dirname = g_path_get_dirname (filename);
1121   g_return_val_if_fail (strcmp (dirname, folder_win32->filename) == 0, NULL);
1122   g_free (dirname);
1123 #endif
1124
1125   info = filename_get_info (filename, folder_win32->types, error);
1126
1127   g_free (filename);
1128
1129   return info;
1130 }
1131
1132 static gboolean
1133 gtk_file_folder_win32_list_children (GtkFileFolder  *folder,
1134                                      GSList        **children,
1135                                      GError        **error)
1136 {
1137   GtkFileFolderWin32 *folder_win32 = GTK_FILE_FOLDER_WIN32 (folder);
1138   GError *tmp_error = NULL;
1139   GDir *dir;
1140
1141   *children = NULL;
1142
1143   dir = g_dir_open (folder_win32->filename, 0, &tmp_error);
1144   if (!dir)
1145     {
1146       g_set_error (error,
1147                    GTK_FILE_SYSTEM_ERROR,
1148                    GTK_FILE_SYSTEM_ERROR_NONEXISTENT,
1149                    "%s",
1150                    tmp_error->message);
1151       
1152       g_error_free (tmp_error);
1153
1154       return FALSE;
1155     }
1156
1157   while (TRUE)
1158     {
1159       const gchar *filename = g_dir_read_name (dir);
1160       gchar *fullname;
1161
1162       if (!filename)
1163         break;
1164
1165       fullname = g_build_filename (folder_win32->filename, filename, NULL);
1166       *children = g_slist_prepend (*children, filename_to_path (fullname));
1167       g_free (fullname);
1168     }
1169
1170   g_dir_close (dir);
1171
1172   *children = g_slist_reverse (*children);
1173   
1174   return TRUE;
1175 }
1176
1177 static GtkFileInfo *
1178 filename_get_info (const gchar     *filename,
1179                    GtkFileInfoType  types,
1180                    GError         **error)
1181 {
1182   GtkFileInfo *info;
1183 #if 0 /* it's dead in GtkFileSystemUnix.c, too */
1184   GtkFileIconType icon_type = GTK_FILE_ICON_REGULAR;
1185 #endif
1186   WIN32_FILE_ATTRIBUTE_DATA wfad;
1187
1188   if (!GetFileAttributesEx (filename, GetFileExInfoStandard, &wfad))
1189     {
1190       gchar *filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
1191       g_set_error (error,
1192                    GTK_FILE_SYSTEM_ERROR,
1193                    GTK_FILE_SYSTEM_ERROR_NONEXISTENT,
1194                    _("error getting information for '%s': %s"),
1195                    filename_utf8 ? filename_utf8 : "???",
1196                    g_win32_error_message (GetLastError ()));
1197       g_free (filename_utf8);
1198       
1199       return NULL;
1200     }
1201
1202   info = gtk_file_info_new ();
1203
1204   if (filename_is_root (filename))
1205     {
1206       if (types & GTK_FILE_INFO_DISPLAY_NAME)
1207         gtk_file_info_set_display_name (info, filename);
1208       
1209       if (types & GTK_FILE_INFO_IS_HIDDEN)
1210         gtk_file_info_set_is_hidden (info, FALSE);
1211     }
1212   else
1213     {
1214       gchar *basename = g_path_get_basename (filename);
1215   
1216       if (types & GTK_FILE_INFO_DISPLAY_NAME)
1217         {
1218           gchar *display_name = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL);
1219           if (!display_name)
1220             display_name = g_strescape (basename, NULL);
1221           
1222           gtk_file_info_set_display_name (info, display_name);
1223           
1224           g_free (display_name);
1225         }
1226       
1227       if (types & GTK_FILE_INFO_IS_HIDDEN)
1228         {
1229             /* win32 convention ... */
1230             gboolean is_hidden = basename[0] == '.';
1231             /* ... _and_ windoze attribute */
1232             is_hidden = is_hidden || !!(wfad.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
1233           gtk_file_info_set_is_hidden (info, is_hidden);
1234         }
1235
1236       g_free (basename);
1237     }
1238
1239   if (types & GTK_FILE_INFO_IS_FOLDER)
1240     {
1241       gtk_file_info_set_is_folder (info, !!(wfad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY));
1242    }
1243
1244 #if 0 /* it's dead in GtkFileSystemUnix.c, too */
1245   if (types & GTK_FILE_INFO_ICON)
1246     {
1247       if (wfad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1248         icon_type = GTK_FILE_ICON_DIRECTORY;
1249
1250       gtk_file_info_set_icon_type (info, icon_type);
1251     }
1252 #endif
1253
1254   if ((types & GTK_FILE_INFO_MIME_TYPE)
1255 #if 0 /* it's dead in GtkFileSystemUnix.c, too */
1256       || ((types & GTK_FILE_INFO_ICON) && icon_type == GTK_FILE_ICON_REGULAR)
1257 #endif
1258      )
1259     {
1260 #if 0
1261       const char *mime_type = xdg_mime_get_mime_type_for_file (filename);
1262       gtk_file_info_set_mime_type (info, mime_type);
1263
1264       if ((types & GTK_FILE_INFO_ICON) && icon_type == GTK_FILE_ICON_REGULAR &&
1265           (statbuf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) &&
1266           (strcmp (mime_type, XDG_MIME_TYPE_UNKNOWN) == 0 ||
1267            strcmp (mime_type, "application/x-executable") == 0 ||
1268            strcmp (mime_type, "application/x-shellscript") == 0))
1269         gtk_file_info_set_icon_type (info, GTK_FILE_ICON_EXECUTABLE);
1270 #endif
1271     }
1272
1273   if (types & GTK_FILE_INFO_MODIFICATION_TIME)
1274     {
1275       GtkFileTime time = wfad.ftLastWriteTime.dwLowDateTime 
1276                        | ((guint64)wfad.ftLastWriteTime.dwHighDateTime) << 32;
1277       /* 100-nanosecond intervals since January 1, 1601, urgh! */
1278       time /= G_GINT64_CONSTANT (10000000); /* now seconds */
1279       time -= G_GINT64_CONSTANT (134774) * 24 * 3600; /* good old Unix time */
1280       gtk_file_info_set_modification_time (info, time);
1281     }
1282
1283   if (types & GTK_FILE_INFO_SIZE)
1284     {
1285       gint64 size = wfad.nFileSizeLow | ((guint64)wfad.nFileSizeHigh) << 32;
1286       gtk_file_info_set_size (info, size);
1287     }
1288   
1289   return info;
1290 }
1291
1292 static gchar *
1293 filename_from_path (const GtkFilePath *path)
1294 {
1295   return g_strdup (gtk_file_path_get_string (path));
1296 }
1297
1298 static GtkFilePath *
1299 filename_to_path (const char *filename)
1300 {
1301   return gtk_file_path_new_dup (filename);
1302 }
1303
1304 static gboolean
1305 filename_is_root (const char *filename)
1306 {
1307   guint len = strlen(filename);
1308
1309   /* accept both forms */
1310
1311   return (   (len == 2 && filename[1] == ':')
1312           || (len == 3 && filename[1] == ':' && (filename[2] == '\\' || filename[2] == '/')));
1313 }
1314