Added. Added. Added. Added.
[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, G_IO_ERROR_NOT_SUPPORTED, _("URIs not supported"));
329   return FALSE;
330 }
331
332 static gboolean
333 g_win32_app_info_should_show (GAppInfo *appinfo,
334                               const char *win32_env)
335 {
336   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
337
338   if (info->no_open_with)
339     return FALSE;
340   
341   return TRUE;
342 }
343
344 static gboolean
345 g_win32_app_info_set_as_default_for_type (GAppInfo    *appinfo,
346                                             const char  *content_type,
347                                             GError     **error)
348 {
349   g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("association changes not supported on win32"));
350   return FALSE;
351 }
352
353 GAppInfo *
354 g_app_info_create_from_commandline (const char *commandline,
355                                     const char *application_name,
356                                     GAppInfoCreateFlags flags,
357                                     GError **error)
358 {
359   g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Association creation not supported on win32"));
360   return NULL;
361 }
362
363
364 static void
365 g_win32_app_info_iface_init (GAppInfoIface *iface)
366 {
367   iface->dup = g_win32_app_info_dup;
368   iface->equal = g_win32_app_info_equal;
369   iface->get_id = g_win32_app_info_get_id;
370   iface->get_name = g_win32_app_info_get_name;
371   iface->get_description = g_win32_app_info_get_description;
372   iface->get_executable = g_win32_app_info_get_executable;
373   iface->get_icon = g_win32_app_info_get_icon;
374   iface->launch = g_win32_app_info_launch;
375   iface->supports_uris = g_win32_app_info_supports_uris;
376   iface->launch_uris = g_win32_app_info_launch_uris;
377   iface->should_show = g_win32_app_info_should_show;
378   iface->set_as_default_for_type = g_win32_app_info_set_as_default_for_type;
379 }
380
381 static void
382 enumerate_open_with_list (HKEY dir_key,
383                           GList **prognames)
384 {
385   DWORD index;
386   wchar_t name[256];
387   DWORD name_len, nbytes;
388   wchar_t data[256];
389   wchar_t *data_alloc;
390   DWORD type;
391
392   /* Must also look inside for a,b,c, + MRUList */
393   index = 0;
394   name_len = 256;
395   nbytes = sizeof (data) - 2;
396   while (RegEnumValueW(dir_key,
397                        index,
398                        name,
399                        &name_len,
400                        0,
401                        &type,
402                        (LPBYTE)data,
403                        &nbytes) == ERROR_SUCCESS)
404     {
405       data[nbytes/2] = '\0';
406       if (type == REG_SZ &&
407           /* Ignore things like MRUList, just look at 'a', 'b', 'c', etc */
408           name_len == 1)
409         {
410           data_alloc = (wchar_t *)g_memdup (data, nbytes + 2);
411           data_alloc[nbytes/2] = 0;
412           *prognames = g_list_prepend (*prognames, data_alloc);
413         }
414       index++;
415       name_len = 256;
416       nbytes = sizeof (data) - 2;
417     }
418   
419   index = 0;
420   name_len = 256;
421   while (RegEnumKeyExW(dir_key,
422                        index,
423                        name,
424                        &name_len,
425                        NULL,
426                        NULL,
427                        NULL,
428                        NULL) == ERROR_SUCCESS)
429     {
430       *prognames = g_list_prepend (*prognames, g_memdup (name, (name_len + 1) * 2));
431       index++;
432       name_len = 256;
433     }
434 }
435
436 static void
437 enumerate_open_with_progids (HKEY dir_key,
438                              GList **progids)
439 {
440   DWORD index;
441   wchar_t name[256];
442   DWORD name_len, type;
443
444   index = 0;
445   name_len = 256;
446   while (RegEnumValueW(dir_key,
447                        index,
448                        name,
449                        &name_len,
450                        0,
451                        &type,
452                        NULL,
453                        0) == ERROR_SUCCESS)
454     {
455       *progids = g_list_prepend (*progids, g_memdup (name, (name_len + 1) * 2));
456       index++;
457       name_len = 256;
458     }
459 }
460
461 static void
462 enumerate_open_with_root (HKEY dir_key,
463                           GList **progids,
464                           GList **prognames)
465 {
466   HKEY reg_key = NULL;
467   
468   if (RegOpenKeyExW (dir_key, L"OpenWithList", 0,
469                      KEY_READ, &reg_key) == ERROR_SUCCESS)
470     {
471       enumerate_open_with_list (reg_key, prognames);
472       RegCloseKey (reg_key);
473     }
474   
475   if (RegOpenKeyExW (dir_key, L"OpenWithProgids", 0,
476                      KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS)
477     {
478       enumerate_open_with_progids (reg_key, progids);
479       RegCloseKey (reg_key);
480     }
481 }
482
483 static gboolean
484 app_info_in_list (GAppInfo *info, GList *l)
485 {
486   while (l != NULL)
487     {
488       if (g_app_info_equal (info, l->data))
489         return TRUE;
490       l = l->next;
491     }
492   return FALSE;
493 }
494
495 GList *
496 g_app_info_get_all_for_type (const char *content_type)
497 {
498   GList *progids = NULL;
499   GList *prognames = NULL;
500   HKEY reg_key, sys_file_assoc_key, reg_key2;
501   wchar_t percieved_type[128];
502   DWORD nchars, key_type;
503   wchar_t *wc_key;
504   GList *l;
505   GList *infos;
506
507   wc_key = g_utf8_to_utf16 (content_type, -1, NULL, NULL, NULL);
508   if (RegOpenKeyExW (HKEY_CLASSES_ROOT, wc_key, 0,
509                      KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS)
510     {
511       enumerate_open_with_root (reg_key, &progids, &prognames);
512
513       nchars = sizeof (percieved_type) / sizeof(wchar_t);
514       if (RegQueryValueExW (reg_key, L"PerceivedType", 0,
515                             &key_type, (LPBYTE) percieved_type, &nchars) == ERROR_SUCCESS)
516         {
517           if (key_type == REG_SZ &&
518               RegOpenKeyExW (HKEY_CLASSES_ROOT, L"SystemFileAssociations", 0,
519                              KEY_QUERY_VALUE, &sys_file_assoc_key) == ERROR_SUCCESS)
520             {
521               if (RegOpenKeyExW (sys_file_assoc_key, percieved_type, 0,
522                                  KEY_QUERY_VALUE, &reg_key2) == ERROR_SUCCESS)
523                 {
524                   enumerate_open_with_root (reg_key2, &progids, &prognames);
525                   RegCloseKey (reg_key2);
526                 }
527
528               RegCloseKey (sys_file_assoc_key);
529             }
530         }
531       RegCloseKey (reg_key);
532     }
533
534   if (RegOpenKeyExW (HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts", 0,
535                      KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS)
536     {
537       if (RegOpenKeyExW (reg_key, wc_key, 0,
538                          KEY_QUERY_VALUE, &reg_key2) == ERROR_SUCCESS)
539         {
540           enumerate_open_with_root (reg_key2, &progids, &prognames);
541           RegCloseKey (reg_key2);
542         }
543       
544       RegCloseKey (reg_key);
545     }
546
547   infos = NULL;
548   for (l = prognames; l != NULL; l = l->next)
549     {
550       GAppInfo *info;
551
552       /* l->data ownership is taken */
553       info = g_desktop_app_info_new_from_id ((wchar_t *)l->data, TRUE);
554       if (app_info_in_list (info, infos))
555         g_object_unref (info);
556       else
557         infos = g_list_prepend (infos, info);
558     }
559   g_list_free (prognames);
560
561   for (l = progids; l != NULL; l = l->next)
562     {
563       GAppInfo *info;
564
565       /* l->data ownership is taken */
566       info = g_desktop_app_info_new_from_id ((wchar_t *)l->data, FALSE);
567       if (app_info_in_list (info, infos))
568         g_object_unref (info);
569       else
570         infos = g_list_prepend (infos, info);
571     }
572   g_list_free (progids);
573   
574   g_free (wc_key);
575   return g_list_reverse (infos);
576 }
577
578 GAppInfo *
579 g_app_info_get_default_for_type (const char *content_type,
580                                  gboolean must_support_uris)
581 {
582   wchar_t *wtype;
583   wchar_t buffer[1024];
584   DWORD buffer_size;
585
586   wtype = g_utf8_to_utf16 (content_type, -1, NULL, NULL, NULL);
587
588   /* Verify that we have some sort of app registered for this type */
589   buffer_size = 1024;
590   if (AssocQueryStringW(0,
591                         REAL_ASSOCSTR_COMMAND,
592                         wtype,
593                         NULL,
594                         buffer,
595                         &buffer_size) == S_OK)
596     /* Takes ownership of wtype */
597     return g_desktop_app_info_new_from_id (wtype, FALSE);
598
599   g_free (wtype);
600   return NULL;
601 }
602
603 GAppInfo *
604 g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
605 {
606   /* TODO: Implement */
607   return NULL;
608 }
609
610 GList *
611 g_app_info_get_all (void)
612 {
613   DWORD index;
614   wchar_t name[256];
615   DWORD name_len;
616   HKEY reg_key;
617   GList *infos;
618   GAppInfo *info;
619
620   if (RegOpenKeyExW (HKEY_CLASSES_ROOT, L"Applications", 0,
621                      KEY_READ, &reg_key) != ERROR_SUCCESS)
622     return NULL;
623
624   infos = NULL;
625   index = 0;
626   name_len = 256;
627   while (RegEnumKeyExW(reg_key,
628                        index,
629                        name,
630                        &name_len,
631                        NULL,
632                        NULL,
633                        NULL,
634                        NULL) == ERROR_SUCCESS)
635     {
636       wchar_t *name_dup = g_memdup (name, (name_len+1)*2);
637       /* name_dup ownership is taken */
638       info = g_desktop_app_info_new_from_id (name_dup, TRUE);
639       infos = g_list_prepend (infos, info);
640       
641       index++;
642       name_len = 256;
643     }
644   
645   RegCloseKey (reg_key);
646
647   return g_list_reverse (infos);
648 }