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