Fix some introspection warnings
[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 (GAppInfo           *appinfo,
250                          GList              *files,
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 = files; l != NULL; l = l->next)
289     {
290       char *path = g_file_get_path (l->data);
291       wchar_t *wfilename = g_utf8_to_utf16 (path, -1, NULL, NULL, NULL);
292
293       g_free (path);
294       
295       memset (&exec_info, 0, sizeof (exec_info));
296       exec_info.cbSize = sizeof (exec_info);
297       exec_info.fMask = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_CLASSKEY;
298       exec_info.lpFile = wfilename;     
299       exec_info.nShow = SW_SHOWNORMAL;
300       exec_info.hkeyClass = class_key;
301       
302       if (!ShellExecuteExW (&exec_info))
303         {
304           char *message_utf8 = g_win32_error_message (GetLastError ());
305
306           g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Error launching application: %s"), message_utf8);
307           g_free (message_utf8);
308           
309           g_free (wfilename);
310           RegCloseKey (class_key);
311           return FALSE;
312         }
313       
314       g_free (wfilename);
315     }
316   
317   RegCloseKey (class_key);
318   
319   return TRUE;
320 }
321
322 static gboolean
323 g_win32_app_info_supports_uris (GAppInfo *appinfo)
324 {
325   return FALSE;
326 }
327
328 static gboolean
329 g_win32_app_info_supports_files (GAppInfo *appinfo)
330 {
331   return TRUE;
332 }
333
334 static gboolean
335 g_win32_app_info_launch_uris (GAppInfo           *appinfo,
336                               GList              *uris,
337                               GAppLaunchContext  *launch_context,
338                               GError            **error)
339 {
340   g_set_error_literal (error, G_IO_ERROR, 
341                        G_IO_ERROR_NOT_SUPPORTED, 
342                        _("URIs not supported"));
343   return FALSE;
344 }
345
346 static gboolean
347 g_win32_app_info_should_show (GAppInfo *appinfo)
348 {
349   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
350
351   if (info->no_open_with)
352     return FALSE;
353   
354   return TRUE;
355 }
356
357 static gboolean
358 g_win32_app_info_set_as_default_for_type (GAppInfo    *appinfo,
359                                           const char  *content_type,
360                                           GError     **error)
361 {
362   g_set_error_literal (error, G_IO_ERROR, 
363                        G_IO_ERROR_NOT_SUPPORTED, 
364                        _("association changes not supported on win32"));
365   return FALSE;
366 }
367
368 GAppInfo *
369 g_app_info_create_from_commandline (const char           *commandline,
370                                     const char           *application_name,
371                                     GAppInfoCreateFlags   flags,
372                                     GError              **error)
373 {
374   g_set_error_literal (error, G_IO_ERROR, 
375                        G_IO_ERROR_NOT_SUPPORTED, 
376                        _("Association creation not supported on win32"));
377   return NULL;
378 }
379
380
381 static void
382 g_win32_app_info_iface_init (GAppInfoIface *iface)
383 {
384   iface->dup = g_win32_app_info_dup;
385   iface->equal = g_win32_app_info_equal;
386   iface->get_id = g_win32_app_info_get_id;
387   iface->get_name = g_win32_app_info_get_name;
388   iface->get_description = g_win32_app_info_get_description;
389   iface->get_executable = g_win32_app_info_get_executable;
390   iface->get_icon = g_win32_app_info_get_icon;
391   iface->launch = g_win32_app_info_launch;
392   iface->supports_uris = g_win32_app_info_supports_uris;
393   iface->supports_files = g_win32_app_info_supports_files;
394   iface->launch_uris = g_win32_app_info_launch_uris;
395   iface->should_show = g_win32_app_info_should_show;
396   iface->set_as_default_for_type = g_win32_app_info_set_as_default_for_type;
397 }
398
399 static void
400 enumerate_open_with_list (HKEY    dir_key,
401                           GList **prognames)
402 {
403   DWORD index;
404   wchar_t name[256];
405   DWORD name_len, nbytes;
406   wchar_t data[256];
407   wchar_t *data_alloc;
408   DWORD type;
409
410   /* Must also look inside for a,b,c, + MRUList */
411   index = 0;
412   name_len = 256;
413   nbytes = sizeof (data) - 2;
414   while (RegEnumValueW (dir_key,
415                         index,
416                         name,
417                         &name_len,
418                         0,
419                         &type,
420                         (LPBYTE)data,
421                         &nbytes) == ERROR_SUCCESS)
422     {
423       data[nbytes/2] = '\0';
424       if (type == REG_SZ &&
425           /* Ignore things like MRUList, just look at 'a', 'b', 'c', etc */
426           name_len == 1)
427         {
428           data_alloc = (wchar_t *)g_memdup (data, nbytes + 2);
429           data_alloc[nbytes/2] = 0;
430           *prognames = g_list_prepend (*prognames, data_alloc);
431         }
432       index++;
433       name_len = 256;
434       nbytes = sizeof (data) - 2;
435     }
436   
437   index = 0;
438   name_len = 256;
439   while (RegEnumKeyExW (dir_key,
440                         index,
441                         name,
442                         &name_len,
443                         NULL,
444                         NULL,
445                         NULL,
446                         NULL) == ERROR_SUCCESS)
447     {
448       *prognames = g_list_prepend (*prognames, g_memdup (name, (name_len + 1) * 2));
449       index++;
450       name_len = 256;
451     }
452 }
453
454 static void
455 enumerate_open_with_progids (HKEY dir_key,
456                              GList **progids)
457 {
458   DWORD index;
459   wchar_t name[256];
460   DWORD name_len, type;
461
462   index = 0;
463   name_len = 256;
464   while (RegEnumValueW (dir_key,
465                         index,
466                         name,
467                         &name_len,
468                         0,
469                         &type,
470                         NULL,
471                         0) == ERROR_SUCCESS)
472     {
473       *progids = g_list_prepend (*progids, g_memdup (name, (name_len + 1) * 2));
474       index++;
475       name_len = 256;
476     }
477 }
478
479 static void
480 enumerate_open_with_root (HKEY    dir_key,
481                           GList **progids,
482                           GList **prognames)
483 {
484   HKEY reg_key = NULL;
485   
486   if (RegOpenKeyExW (dir_key, L"OpenWithList", 0,
487                      KEY_READ, &reg_key) == ERROR_SUCCESS)
488     {
489       enumerate_open_with_list (reg_key, prognames);
490       RegCloseKey (reg_key);
491     }
492   
493   if (RegOpenKeyExW (dir_key, L"OpenWithProgids", 0,
494                      KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS)
495     {
496       enumerate_open_with_progids (reg_key, progids);
497       RegCloseKey (reg_key);
498     }
499 }
500
501 static gboolean
502 app_info_in_list (GAppInfo *info, 
503                   GList    *list)
504 {
505   while (list != NULL)
506     {
507       if (g_app_info_equal (info, list->data))
508         return TRUE;
509       list = list->next;
510     }
511   return FALSE;
512 }
513
514 GList *
515 g_app_info_get_all_for_type (const char *content_type)
516 {
517   GList *progids = NULL;
518   GList *prognames = NULL;
519   HKEY reg_key, sys_file_assoc_key, reg_key2;
520   wchar_t percieved_type[128];
521   DWORD nchars, key_type;
522   wchar_t *wc_key;
523   GList *l;
524   GList *infos;
525
526   wc_key = g_utf8_to_utf16 (content_type, -1, NULL, NULL, NULL);
527   if (RegOpenKeyExW (HKEY_CLASSES_ROOT, wc_key, 0,
528                      KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS)
529     {
530       enumerate_open_with_root (reg_key, &progids, &prognames);
531
532       nchars = sizeof (percieved_type) / sizeof(wchar_t);
533       if (RegQueryValueExW (reg_key, L"PerceivedType", 0,
534                             &key_type, (LPBYTE) percieved_type, &nchars) == ERROR_SUCCESS)
535         {
536           if (key_type == REG_SZ &&
537               RegOpenKeyExW (HKEY_CLASSES_ROOT, L"SystemFileAssociations", 0,
538                              KEY_QUERY_VALUE, &sys_file_assoc_key) == ERROR_SUCCESS)
539             {
540               if (RegOpenKeyExW (sys_file_assoc_key, percieved_type, 0,
541                                  KEY_QUERY_VALUE, &reg_key2) == ERROR_SUCCESS)
542                 {
543                   enumerate_open_with_root (reg_key2, &progids, &prognames);
544                   RegCloseKey (reg_key2);
545                 }
546
547               RegCloseKey (sys_file_assoc_key);
548             }
549         }
550       RegCloseKey (reg_key);
551     }
552
553   if (RegOpenKeyExW (HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts", 0,
554                      KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS)
555     {
556       if (RegOpenKeyExW (reg_key, wc_key, 0,
557                          KEY_QUERY_VALUE, &reg_key2) == ERROR_SUCCESS)
558         {
559           enumerate_open_with_root (reg_key2, &progids, &prognames);
560           RegCloseKey (reg_key2);
561         }
562       
563       RegCloseKey (reg_key);
564     }
565
566   infos = NULL;
567   for (l = prognames; l != NULL; l = l->next)
568     {
569       GAppInfo *info;
570
571       /* l->data ownership is taken */
572       info = g_desktop_app_info_new_from_id ((wchar_t *)l->data, TRUE);
573       if (app_info_in_list (info, infos))
574         g_object_unref (info);
575       else
576         infos = g_list_prepend (infos, info);
577     }
578   g_list_free (prognames);
579
580   for (l = progids; l != NULL; l = l->next)
581     {
582       GAppInfo *info;
583
584       /* l->data ownership is taken */
585       info = g_desktop_app_info_new_from_id ((wchar_t *)l->data, FALSE);
586       if (app_info_in_list (info, infos))
587         g_object_unref (info);
588       else
589         infos = g_list_prepend (infos, info);
590     }
591   g_list_free (progids);
592   
593   g_free (wc_key);
594   return g_list_reverse (infos);
595 }
596
597 GList *
598 g_app_info_get_recommended_for_type (const char *content_type)
599 {
600   /* FIXME: this should generate a list of applications that are registered
601    * as direct handlers for the given content type, without using MIME subclassing.
602    * See g_app_info_get_recommended_for_type() in gdesktopappinfo.c for a reference
603    * UNIX implementation.
604    */
605   return g_app_info_get_all_for_type (content_type);
606 }
607
608 GList *
609 g_app_info_get_fallback_for_type (const char *content_type)
610 {
611   /* FIXME: this should generate a list of applications that are registered
612    * as handlers for a superclass of the given content type, but are not
613    * direct handlers for the content type itself. See g_app_info_get_fallback_for_type()
614    * in gdesktopappinfo.c for a reference UNIX implementation.
615    */
616   return g_app_info_get_all_for_type (content_type);
617 }
618
619 GAppInfo *
620 g_app_info_get_default_for_type (const char *content_type,
621                                  gboolean    must_support_uris)
622 {
623   wchar_t *wtype;
624   wchar_t buffer[1024];
625   DWORD buffer_size;
626
627   wtype = g_utf8_to_utf16 (content_type, -1, NULL, NULL, NULL);
628
629   /* Verify that we have some sort of app registered for this type */
630 #ifdef AssocQueryString
631   buffer_size = 1024;
632   if (AssocQueryStringW (0,
633                          REAL_ASSOCSTR_COMMAND,
634                          wtype,
635                          NULL,
636                          buffer,
637                          &buffer_size) == S_OK)
638     /* Takes ownership of wtype */
639     return g_desktop_app_info_new_from_id (wtype, FALSE);
640 #endif
641
642   g_free (wtype);
643   return NULL;
644 }
645
646 GAppInfo *
647 g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
648 {
649   /* TODO: Implement */
650   return NULL;
651 }
652
653 GList *
654 g_app_info_get_all (void)
655 {
656   DWORD index;
657   wchar_t name[256];
658   DWORD name_len;
659   HKEY reg_key;
660   GList *infos;
661   GAppInfo *info;
662
663   if (RegOpenKeyExW (HKEY_CLASSES_ROOT, L"Applications", 0,
664                      KEY_READ, &reg_key) != ERROR_SUCCESS)
665     return NULL;
666
667   infos = NULL;
668   index = 0;
669   name_len = 256;
670   while (RegEnumKeyExW (reg_key,
671                         index,
672                         name,
673                         &name_len,
674                         NULL,
675                         NULL,
676                         NULL,
677                         NULL) == ERROR_SUCCESS)
678     {
679       wchar_t *name_dup = g_memdup (name, (name_len+1)*2);
680       /* name_dup ownership is taken */
681       info = g_desktop_app_info_new_from_id (name_dup, TRUE);
682       infos = g_list_prepend (infos, info);
683       
684       index++;
685       name_len = 256;
686     }
687   
688   RegCloseKey (reg_key);
689
690   return g_list_reverse (infos);
691 }
692
693 void
694 g_app_info_reset_type_associations (const char *content_type)
695 {
696   /* nothing to do */
697 }