cleanup
[platform/upstream/glib.git] / gio / gwin32appinfo.c
1 /* GIO - GLib Input, Output and Streaming Library
2  * 
3  * Copyright (C) 2006-2007 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
16  * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
17  *
18  * Author: Alexander Larsson <alexl@redhat.com>
19  */
20
21 #include "config.h"
22
23 #include <string.h>
24
25 #include "gcontenttype.h"
26 #include "gwin32appinfo.h"
27 #include "gappinfo.h"
28 #include "gioerror.h"
29 #include "gfile.h"
30 #include <glib/gstdio.h>
31 #include "glibintl.h"
32
33 #include <windows.h>
34 #include <shlwapi.h>
35
36
37 #ifndef ASSOCF_INIT_BYEXENAME
38 #define ASSOCF_INIT_BYEXENAME 0x00000002
39 #endif
40
41 /* These were wrong in MingW */
42 #define REAL_ASSOCSTR_COMMAND 1
43 #define REAL_ASSOCSTR_EXECUTABLE 2
44 #define REAL_ASSOCSTR_FRIENDLYDOCNAME 3
45 #define REAL_ASSOCSTR_FRIENDLYAPPNAME 4
46
47 #ifndef AssocQueryString
48 #pragma message("AssocQueryString not available with SDK used")
49 #endif
50
51 static void g_win32_app_info_iface_init (GAppInfoIface *iface);
52
53 struct _GWin32AppInfo
54 {
55   GObject parent_instance;
56   wchar_t *id;
57   char *id_utf8;
58   gboolean id_is_exename;
59   char *executable;
60   char *name;
61   gboolean no_open_with;
62 };
63
64 G_DEFINE_TYPE_WITH_CODE (GWin32AppInfo, g_win32_app_info, G_TYPE_OBJECT,
65                          G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO,
66                                                 g_win32_app_info_iface_init))
67
68
69 static void
70 g_win32_app_info_finalize (GObject *object)
71 {
72   GWin32AppInfo *info;
73
74   info = G_WIN32_APP_INFO (object);
75
76   g_free (info->id);
77   g_free (info->id_utf8);
78   g_free (info->name);
79   g_free (info->executable);
80
81   G_OBJECT_CLASS (g_win32_app_info_parent_class)->finalize (object);
82 }
83
84 static void
85 g_win32_app_info_class_init (GWin32AppInfoClass *klass)
86 {
87   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
88   
89   gobject_class->finalize = g_win32_app_info_finalize;
90 }
91
92 static void
93 g_win32_app_info_init (GWin32AppInfo *local)
94 {
95 }
96
97 static GAppInfo *
98 g_desktop_app_info_new_from_id (wchar_t *id /* takes ownership */,
99                                 gboolean id_is_exename)
100 {
101 #ifdef AssocQueryString
102   ASSOCF flags;
103 #endif
104   wchar_t buffer[1024];
105   DWORD buffer_size;
106   GWin32AppInfo *info;
107   HKEY app_key;
108   
109   info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL);
110   info->id = id; /* Takes ownership */
111   info->id_utf8 = g_utf16_to_utf8 (id, -1, NULL, NULL, NULL);  
112   info->id_is_exename = id_is_exename;
113
114 #ifdef AssocQueryString  
115   flags = 0;
116   if (id_is_exename)
117     flags |= ASSOCF_INIT_BYEXENAME;
118
119   buffer_size = 1024;
120   if (AssocQueryStringW(flags,
121                         REAL_ASSOCSTR_EXECUTABLE,
122                         id,
123                         NULL,
124                         buffer,
125                         &buffer_size) == S_OK)
126     info->executable = g_utf16_to_utf8 (buffer, -1, NULL, NULL, NULL);
127  
128   buffer_size = 1024;
129   if (AssocQueryStringW(flags,
130                         REAL_ASSOCSTR_FRIENDLYAPPNAME,
131                         id,
132                         NULL,
133                         buffer,
134                         &buffer_size) == S_OK)
135     info->name = g_utf16_to_utf8 (buffer, -1, NULL, NULL, NULL);
136 #endif
137
138   if (info->name == NULL)
139     {
140       /* TODO: Should look up name from executable resources */
141       if (info->executable)
142         info->name = g_path_get_basename (info->executable);
143       else
144         info->name = g_strdup (info->id_utf8);
145     }
146
147 #ifdef AssocQueryString
148   if (AssocQueryKeyW(flags,
149                      ASSOCKEY_APP,
150                      info->id,
151                      NULL,
152                      &app_key) == S_OK)
153     {
154       if (RegQueryValueExW (app_key, L"NoOpenWith", 0,
155                             NULL, NULL, NULL) == ERROR_SUCCESS)
156         info->no_open_with = TRUE;
157       RegCloseKey (app_key);
158     }
159 #endif
160   
161   return G_APP_INFO (info);
162 }
163
164 static wchar_t *
165 dup_wstring (wchar_t *str)
166 {
167   gsize len;
168   for (len = 0; str[len] != 0; len++)
169     ;
170   return (wchar_t *)g_memdup (str, (len + 1) * 2);
171 }
172
173 static GAppInfo *
174 g_win32_app_info_dup (GAppInfo *appinfo)
175 {
176   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
177   GWin32AppInfo *new_info;
178   
179   new_info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL);
180
181   new_info->id = dup_wstring (info->id);
182   new_info->id_utf8 = g_strdup (info->id_utf8);
183   new_info->id_is_exename = info->id_is_exename;
184   new_info->name = g_strdup (info->name);
185   new_info->executable = g_strdup (info->executable);
186   new_info->no_open_with = info->no_open_with;
187   
188   return G_APP_INFO (new_info);
189 }
190
191 static gboolean
192 g_win32_app_info_equal (GAppInfo *appinfo1,
193                         GAppInfo *appinfo2)
194 {
195   GWin32AppInfo *info1 = G_WIN32_APP_INFO (appinfo1);
196   GWin32AppInfo *info2 = G_WIN32_APP_INFO (appinfo2);
197
198   if (info1->executable == NULL ||
199       info2->executable == NULL)
200     return FALSE;
201   
202   return strcmp (info1->executable, info2->executable) == 0;
203 }
204
205 static const char *
206 g_win32_app_info_get_id (GAppInfo *appinfo)
207 {
208   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
209
210   return info->id_utf8;
211 }
212
213 static const char *
214 g_win32_app_info_get_name (GAppInfo *appinfo)
215 {
216   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
217
218   if (info->name == NULL)
219     return _("Unnamed");
220   
221   return info->name;
222 }
223
224 static const char *
225 g_win32_app_info_get_description (GAppInfo *appinfo)
226 {
227   /* Win32 has no app descriptions */
228   return NULL;
229 }
230
231 static const char *
232 g_win32_app_info_get_executable (GAppInfo *appinfo)
233 {
234   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
235   
236   return info->executable;
237 }
238
239 static GIcon *
240 g_win32_app_info_get_icon (GAppInfo *appinfo)
241 {
242   /* GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo); */
243
244   /* TODO: How to handle icons */
245   return NULL;
246 }
247
248 static gboolean
249 g_win32_app_info_launch_locations (GAppInfo           *appinfo,
250                                    GList              *locations,
251                                    GAppLaunchContext  *launch_context,
252                                    GError            **error)
253 {
254   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
255 #ifdef AssocQueryString
256   ASSOCF flags;
257 #endif
258   HKEY class_key;
259   SHELLEXECUTEINFOW exec_info = {0};
260   GList *l;
261
262   /* TODO:  What might startup_id mean on win32? */
263 #ifdef AssocQueryString  
264   flags = 0;
265   if (info->id_is_exename)
266     flags |= ASSOCF_INIT_BYEXENAME;
267
268   if (AssocQueryKeyW (flags,
269                       ASSOCKEY_SHELLEXECCLASS,
270                       info->id,
271                       NULL,
272                       &class_key) != S_OK)
273     {
274       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Can't find application"));
275       return FALSE;
276     }
277 #endif
278
279   /* FIXME: Need to do something with
280    * g_app_launch_context_get_environment()... ShellExecuteExW()
281    * doesn't have any way to pass an environment though. We need to
282    * either (a) update environment, ShellExecuteExW(), revert
283    * environment; or (b) find an API to figure out what app
284    * ShellExecuteExW() would launch, and then use g_spawn_async()
285    * instead.
286    */
287
288   for (l = locations; l != NULL; l = l->next)
289     {
290       wchar_t *wloc = g_utf8_to_utf16 (l->data, -1, NULL, NULL, NULL);
291       
292       memset (&exec_info, 0, sizeof (exec_info));
293       exec_info.cbSize = sizeof (exec_info);
294       exec_info.fMask = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_CLASSKEY;
295       exec_info.lpFile = wloc;
296       exec_info.nShow = SW_SHOWNORMAL;
297       exec_info.hkeyClass = class_key;
298       
299       if (!ShellExecuteExW (&exec_info))
300         {
301           char *message_utf8 = g_win32_error_message (GetLastError ());
302
303           g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Error launching application: %s"), message_utf8);
304           g_free (message_utf8);
305           
306           g_free (wloc);
307           RegCloseKey (class_key);
308           return FALSE;
309         }
310       
311       g_free (wloc);
312     }
313   
314   RegCloseKey (class_key);
315   
316   return TRUE;
317 }
318
319 static gboolean
320 g_win32_app_info_supports_uris (GAppInfo *appinfo)
321 {
322   /* TODO: is there some way to determine this on windows? */
323   return TRUE;
324 }
325
326 static gboolean
327 g_win32_app_info_supports_files (GAppInfo *appinfo)
328 {
329   return TRUE;
330 }
331
332 static gboolean
333 g_win32_app_info_launch (GAppInfo           *appinfo,
334                          GList              *files,
335                          GAppLaunchContext  *launch_context,
336                          GError            **error)
337 {
338   gboolean success;
339   GList *paths = g_list_copy_deep (files, (GCopyFunc) g_file_get_path,
340                                    NULL);
341
342   success = g_win32_app_info_launch_locations (appinfo, paths,
343                                                launch_context, error);
344
345   g_list_free_full (paths, g_free);
346
347   return success;
348 }
349
350 static gboolean
351 g_win32_app_info_launch_uris (GAppInfo           *appinfo,
352                               GList              *uris,
353                               GAppLaunchContext  *launch_context,
354                               GError            **error)
355 {
356   return g_win32_app_info_launch_locations (appinfo, uris,
357                                             launch_context, error);
358 }
359
360 static gboolean
361 g_win32_app_info_should_show (GAppInfo *appinfo)
362 {
363   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
364
365   if (info->no_open_with)
366     return FALSE;
367   
368   return TRUE;
369 }
370
371 static gboolean
372 g_win32_app_info_set_as_default_for_type (GAppInfo    *appinfo,
373                                           const char  *content_type,
374                                           GError     **error)
375 {
376   g_set_error_literal (error, G_IO_ERROR, 
377                        G_IO_ERROR_NOT_SUPPORTED, 
378                        _("association changes not supported on win32"));
379   return FALSE;
380 }
381
382 GAppInfo *
383 g_app_info_create_from_commandline (const char           *commandline,
384                                     const char           *application_name,
385                                     GAppInfoCreateFlags   flags,
386                                     GError              **error)
387 {
388   g_set_error_literal (error, G_IO_ERROR, 
389                        G_IO_ERROR_NOT_SUPPORTED, 
390                        _("Association creation not supported on win32"));
391   return NULL;
392 }
393
394
395 static void
396 g_win32_app_info_iface_init (GAppInfoIface *iface)
397 {
398   iface->dup = g_win32_app_info_dup;
399   iface->equal = g_win32_app_info_equal;
400   iface->get_id = g_win32_app_info_get_id;
401   iface->get_name = g_win32_app_info_get_name;
402   iface->get_description = g_win32_app_info_get_description;
403   iface->get_executable = g_win32_app_info_get_executable;
404   iface->get_icon = g_win32_app_info_get_icon;
405   iface->launch = g_win32_app_info_launch;
406   iface->supports_uris = g_win32_app_info_supports_uris;
407   iface->supports_files = g_win32_app_info_supports_files;
408   iface->launch_uris = g_win32_app_info_launch_uris;
409   iface->should_show = g_win32_app_info_should_show;
410   iface->set_as_default_for_type = g_win32_app_info_set_as_default_for_type;
411 }
412
413 static void
414 enumerate_open_with_list (HKEY    dir_key,
415                           GList **prognames)
416 {
417   DWORD index;
418   wchar_t name[256];
419   DWORD name_len, nbytes;
420   wchar_t data[256];
421   wchar_t *data_alloc;
422   DWORD type;
423
424   /* Must also look inside for a,b,c, + MRUList */
425   index = 0;
426   name_len = 256;
427   nbytes = sizeof (data) - 2;
428   while (RegEnumValueW (dir_key,
429                         index,
430                         name,
431                         &name_len,
432                         0,
433                         &type,
434                         (LPBYTE)data,
435                         &nbytes) == ERROR_SUCCESS)
436     {
437       data[nbytes/2] = '\0';
438       if (type == REG_SZ &&
439           /* Ignore things like MRUList, just look at 'a', 'b', 'c', etc */
440           name_len == 1)
441         {
442           data_alloc = (wchar_t *)g_memdup (data, nbytes + 2);
443           data_alloc[nbytes/2] = 0;
444           *prognames = g_list_prepend (*prognames, data_alloc);
445         }
446       index++;
447       name_len = 256;
448       nbytes = sizeof (data) - 2;
449     }
450   
451   index = 0;
452   name_len = 256;
453   while (RegEnumKeyExW (dir_key,
454                         index,
455                         name,
456                         &name_len,
457                         NULL,
458                         NULL,
459                         NULL,
460                         NULL) == ERROR_SUCCESS)
461     {
462       *prognames = g_list_prepend (*prognames, g_memdup (name, (name_len + 1) * 2));
463       index++;
464       name_len = 256;
465     }
466 }
467
468 static void
469 enumerate_open_with_progids (HKEY dir_key,
470                              GList **progids)
471 {
472   DWORD index;
473   wchar_t name[256];
474   DWORD name_len, type;
475
476   index = 0;
477   name_len = 256;
478   while (RegEnumValueW (dir_key,
479                         index,
480                         name,
481                         &name_len,
482                         0,
483                         &type,
484                         NULL,
485                         0) == ERROR_SUCCESS)
486     {
487       *progids = g_list_prepend (*progids, g_memdup (name, (name_len + 1) * 2));
488       index++;
489       name_len = 256;
490     }
491 }
492
493 static void
494 enumerate_open_with_root (HKEY    dir_key,
495                           GList **progids,
496                           GList **prognames)
497 {
498   HKEY reg_key = NULL;
499   
500   if (RegOpenKeyExW (dir_key, L"OpenWithList", 0,
501                      KEY_READ, &reg_key) == ERROR_SUCCESS)
502     {
503       enumerate_open_with_list (reg_key, prognames);
504       RegCloseKey (reg_key);
505     }
506   
507   if (RegOpenKeyExW (dir_key, L"OpenWithProgids", 0,
508                      KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS)
509     {
510       enumerate_open_with_progids (reg_key, progids);
511       RegCloseKey (reg_key);
512     }
513 }
514
515 static gboolean
516 app_info_in_list (GAppInfo *info, 
517                   GList    *list)
518 {
519   while (list != NULL)
520     {
521       if (g_app_info_equal (info, list->data))
522         return TRUE;
523       list = list->next;
524     }
525   return FALSE;
526 }
527
528 GList *
529 g_app_info_get_all_for_type (const char *content_type)
530 {
531   GList *progids = NULL;
532   GList *prognames = NULL;
533   HKEY reg_key, sys_file_assoc_key, reg_key2;
534   wchar_t percieved_type[128];
535   DWORD nchars, key_type;
536   wchar_t *wc_key;
537   GList *l;
538   GList *infos;
539
540   wc_key = g_utf8_to_utf16 (content_type, -1, NULL, NULL, NULL);
541   if (RegOpenKeyExW (HKEY_CLASSES_ROOT, wc_key, 0,
542                      KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS)
543     {
544       enumerate_open_with_root (reg_key, &progids, &prognames);
545
546       nchars = sizeof (percieved_type) / sizeof(wchar_t);
547       if (RegQueryValueExW (reg_key, L"PerceivedType", 0,
548                             &key_type, (LPBYTE) percieved_type, &nchars) == ERROR_SUCCESS)
549         {
550           if (key_type == REG_SZ &&
551               RegOpenKeyExW (HKEY_CLASSES_ROOT, L"SystemFileAssociations", 0,
552                              KEY_QUERY_VALUE, &sys_file_assoc_key) == ERROR_SUCCESS)
553             {
554               if (RegOpenKeyExW (sys_file_assoc_key, percieved_type, 0,
555                                  KEY_QUERY_VALUE, &reg_key2) == ERROR_SUCCESS)
556                 {
557                   enumerate_open_with_root (reg_key2, &progids, &prognames);
558                   RegCloseKey (reg_key2);
559                 }
560
561               RegCloseKey (sys_file_assoc_key);
562             }
563         }
564       RegCloseKey (reg_key);
565     }
566
567   if (RegOpenKeyExW (HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts", 0,
568                      KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS)
569     {
570       if (RegOpenKeyExW (reg_key, wc_key, 0,
571                          KEY_QUERY_VALUE, &reg_key2) == ERROR_SUCCESS)
572         {
573           enumerate_open_with_root (reg_key2, &progids, &prognames);
574           RegCloseKey (reg_key2);
575         }
576       
577       RegCloseKey (reg_key);
578     }
579
580   infos = NULL;
581   for (l = prognames; l != NULL; l = l->next)
582     {
583       GAppInfo *info;
584
585       /* l->data ownership is taken */
586       info = g_desktop_app_info_new_from_id ((wchar_t *)l->data, TRUE);
587       if (app_info_in_list (info, infos))
588         g_object_unref (info);
589       else
590         infos = g_list_prepend (infos, info);
591     }
592   g_list_free (prognames);
593
594   for (l = progids; l != NULL; l = l->next)
595     {
596       GAppInfo *info;
597
598       /* l->data ownership is taken */
599       info = g_desktop_app_info_new_from_id ((wchar_t *)l->data, FALSE);
600       if (app_info_in_list (info, infos))
601         g_object_unref (info);
602       else
603         infos = g_list_prepend (infos, info);
604     }
605   g_list_free (progids);
606   
607   g_free (wc_key);
608   return g_list_reverse (infos);
609 }
610
611 GList *
612 g_app_info_get_recommended_for_type (const char *content_type)
613 {
614   /* FIXME: this should generate a list of applications that are registered
615    * as direct handlers for the given content type, without using MIME subclassing.
616    * See g_app_info_get_recommended_for_type() in gdesktopappinfo.c for a reference
617    * UNIX implementation.
618    */
619   return g_app_info_get_all_for_type (content_type);
620 }
621
622 GList *
623 g_app_info_get_fallback_for_type (const char *content_type)
624 {
625   /* FIXME: this should generate a list of applications that are registered
626    * as handlers for a superclass of the given content type, but are not
627    * direct handlers for the content type itself. See g_app_info_get_fallback_for_type()
628    * in gdesktopappinfo.c for a reference UNIX implementation.
629    */
630   return g_app_info_get_all_for_type (content_type);
631 }
632
633 /*
634  * The windows api (AssocQueryString) doesn't distinguish between uri schemes
635  * and file type extensions here, so we use the same implementation for both
636  * g_app_info_get_default_for_type and g_app_info_get_default_for_uri_scheme
637  */
638 static GAppInfo *
639 get_default_for_association (const char *association)
640 {
641   wchar_t *wtype;
642   wchar_t buffer[1024];
643   DWORD buffer_size;
644
645   wtype = g_utf8_to_utf16 (association, -1, NULL, NULL, NULL);
646
647   /* Verify that we have some sort of app registered for this type */
648 #ifdef AssocQueryString
649   buffer_size = 1024;
650   if (AssocQueryStringW (0,
651                          REAL_ASSOCSTR_COMMAND,
652                          wtype,
653                          NULL,
654                          buffer,
655                          &buffer_size) == S_OK)
656     /* Takes ownership of wtype */
657     return g_desktop_app_info_new_from_id (wtype, FALSE);
658 #endif
659
660   g_free (wtype);
661   return NULL;
662 }
663
664 GAppInfo *
665 g_app_info_get_default_for_type (const char *content_type,
666                                  gboolean    must_support_uris)
667 {
668   return get_default_for_association (content_type);
669 }
670
671 GAppInfo *
672 g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
673 {
674   return get_default_for_association (uri_scheme);
675 }
676
677 GList *
678 g_app_info_get_all (void)
679 {
680   DWORD index;
681   wchar_t name[256];
682   DWORD name_len;
683   HKEY reg_key;
684   GList *infos;
685   GAppInfo *info;
686
687   if (RegOpenKeyExW (HKEY_CLASSES_ROOT, L"Applications", 0,
688                      KEY_READ, &reg_key) != ERROR_SUCCESS)
689     return NULL;
690
691   infos = NULL;
692   index = 0;
693   name_len = 256;
694   while (RegEnumKeyExW (reg_key,
695                         index,
696                         name,
697                         &name_len,
698                         NULL,
699                         NULL,
700                         NULL,
701                         NULL) == ERROR_SUCCESS)
702     {
703       wchar_t *name_dup = g_memdup (name, (name_len+1)*2);
704       /* name_dup ownership is taken */
705       info = g_desktop_app_info_new_from_id (name_dup, TRUE);
706       infos = g_list_prepend (infos, info);
707       
708       index++;
709       name_len = 256;
710     }
711   
712   RegCloseKey (reg_key);
713
714   return g_list_reverse (infos);
715 }
716
717 void
718 g_app_info_reset_type_associations (const char *content_type)
719 {
720   /* nothing to do */
721 }