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