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 "gappinfo.h"
30#include "gioerror.h"
31#include "gfile.h"
32#include <glib/gstdio.h>
33#include "glibintl.h"
34
35#include <windows.h>
36#include <shlwapi.h>
37
38#include "gioalias.h"
39
40#ifndef ASSOCF_INIT_BYEXENAME
41#define ASSOCF_INIT_BYEXENAME 0x00000002
42#endif
43
44/* These were wrong in MingW */
45#define REAL_ASSOCSTR_COMMAND 1
46#define REAL_ASSOCSTR_EXECUTABLE 2
47#define REAL_ASSOCSTR_FRIENDLYDOCNAME 3
48#define REAL_ASSOCSTR_FRIENDLYAPPNAME 4
49
50#ifndef AssocQueryString
51#pragma message("AssocQueryString not available with SDK used")
52#endif
53
54static void g_win32_app_info_iface_init (GAppInfoIface *iface);
55
56struct _GWin32AppInfo
57{
58  GObject parent_instance;
59  wchar_t *id;
60  char *id_utf8;
61  gboolean id_is_exename;
62  char *executable;
63  char *name;
64  gboolean no_open_with;
65};
66
67G_DEFINE_TYPE_WITH_CODE (GWin32AppInfo, g_win32_app_info, G_TYPE_OBJECT,
68			 G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO,
69						g_win32_app_info_iface_init))
70
71
72static void
73g_win32_app_info_finalize (GObject *object)
74{
75  GWin32AppInfo *info;
76
77  info = G_WIN32_APP_INFO (object);
78
79  g_free (info->id);
80  g_free (info->id_utf8);
81  g_free (info->name);
82  g_free (info->executable);
83
84  G_OBJECT_CLASS (g_win32_app_info_parent_class)->finalize (object);
85}
86
87static void
88g_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
95static void
96g_win32_app_info_init (GWin32AppInfo *local)
97{
98}
99
100static GAppInfo *
101g_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
167static wchar_t *
168dup_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
176static GAppInfo *
177g_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
194static gboolean
195g_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
208static const char *
209g_win32_app_info_get_id (GAppInfo *appinfo)
210{
211  GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
212
213  return info->id_utf8;
214}
215
216static const char *
217g_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
227static const char *
228g_win32_app_info_get_description (GAppInfo *appinfo)
229{
230  /* Win32 has no app descriptions */
231  return NULL;
232}
233
234static const char *
235g_win32_app_info_get_executable (GAppInfo *appinfo)
236{
237  GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
238
239  return info->executable;
240}
241
242static GIcon *
243g_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
251static gboolean
252g_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_literal (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	  char *message_utf8 = g_win32_error_message (GetLastError ());
299
300	  g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Error launching application: %s"), message_utf8);
301	  g_free (message_utf8);
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
316static gboolean
317g_win32_app_info_supports_uris (GAppInfo *appinfo)
318{
319  return FALSE;
320}
321
322static gboolean
323g_win32_app_info_supports_files (GAppInfo *appinfo)
324{
325  return TRUE;
326}
327
328static gboolean
329g_win32_app_info_launch_uris (GAppInfo           *appinfo,
330			      GList              *uris,
331			      GAppLaunchContext  *launch_context,
332			      GError            **error)
333{
334  g_set_error_literal (error, G_IO_ERROR,
335                       G_IO_ERROR_NOT_SUPPORTED,
336                       _("URIs not supported"));
337  return FALSE;
338}
339
340static gboolean
341g_win32_app_info_should_show (GAppInfo *appinfo)
342{
343  GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
344
345  if (info->no_open_with)
346    return FALSE;
347
348  return TRUE;
349}
350
351static gboolean
352g_win32_app_info_set_as_default_for_type (GAppInfo    *appinfo,
353                                          const char  *content_type,
354                                          GError     **error)
355{
356  g_set_error_literal (error, G_IO_ERROR,
357                       G_IO_ERROR_NOT_SUPPORTED,
358                       _("association changes not supported on win32"));
359  return FALSE;
360}
361
362GAppInfo *
363g_app_info_create_from_commandline (const char           *commandline,
364				    const char           *application_name,
365				    GAppInfoCreateFlags   flags,
366				    GError              **error)
367{
368  g_set_error_literal (error, G_IO_ERROR,
369                       G_IO_ERROR_NOT_SUPPORTED,
370                       _("Association creation not supported on win32"));
371  return NULL;
372}
373
374
375static void
376g_win32_app_info_iface_init (GAppInfoIface *iface)
377{
378  iface->dup = g_win32_app_info_dup;
379  iface->equal = g_win32_app_info_equal;
380  iface->get_id = g_win32_app_info_get_id;
381  iface->get_name = g_win32_app_info_get_name;
382  iface->get_description = g_win32_app_info_get_description;
383  iface->get_executable = g_win32_app_info_get_executable;
384  iface->get_icon = g_win32_app_info_get_icon;
385  iface->launch = g_win32_app_info_launch;
386  iface->supports_uris = g_win32_app_info_supports_uris;
387  iface->supports_files = g_win32_app_info_supports_files;
388  iface->launch_uris = g_win32_app_info_launch_uris;
389  iface->should_show = g_win32_app_info_should_show;
390  iface->set_as_default_for_type = g_win32_app_info_set_as_default_for_type;
391}
392
393static void
394enumerate_open_with_list (HKEY    dir_key,
395			  GList **prognames)
396{
397  DWORD index;
398  wchar_t name[256];
399  DWORD name_len, nbytes;
400  wchar_t data[256];
401  wchar_t *data_alloc;
402  DWORD type;
403
404  /* Must also look inside for a,b,c, + MRUList */
405  index = 0;
406  name_len = 256;
407  nbytes = sizeof (data) - 2;
408  while (RegEnumValueW (dir_key,
409		        index,
410		        name,
411		        &name_len,
412		        0,
413		        &type,
414		        (LPBYTE)data,
415		        &nbytes) == ERROR_SUCCESS)
416    {
417      data[nbytes/2] = '\0';
418      if (type == REG_SZ &&
419	  /* Ignore things like MRUList, just look at 'a', 'b', 'c', etc */
420	  name_len == 1)
421	{
422	  data_alloc = (wchar_t *)g_memdup (data, nbytes + 2);
423	  data_alloc[nbytes/2] = 0;
424	  *prognames = g_list_prepend (*prognames, data_alloc);
425	}
426      index++;
427      name_len = 256;
428      nbytes = sizeof (data) - 2;
429    }
430
431  index = 0;
432  name_len = 256;
433  while (RegEnumKeyExW (dir_key,
434		        index,
435		        name,
436		        &name_len,
437		        NULL,
438		        NULL,
439		        NULL,
440		        NULL) == ERROR_SUCCESS)
441    {
442      *prognames = g_list_prepend (*prognames, g_memdup (name, (name_len + 1) * 2));
443      index++;
444      name_len = 256;
445    }
446}
447
448static void
449enumerate_open_with_progids (HKEY dir_key,
450			     GList **progids)
451{
452  DWORD index;
453  wchar_t name[256];
454  DWORD name_len, type;
455
456  index = 0;
457  name_len = 256;
458  while (RegEnumValueW (dir_key,
459		        index,
460		        name,
461		        &name_len,
462		        0,
463		        &type,
464		        NULL,
465		        0) == ERROR_SUCCESS)
466    {
467      *progids = g_list_prepend (*progids, g_memdup (name, (name_len + 1) * 2));
468      index++;
469      name_len = 256;
470    }
471}
472
473static void
474enumerate_open_with_root (HKEY    dir_key,
475			  GList **progids,
476			  GList **prognames)
477{
478  HKEY reg_key = NULL;
479
480  if (RegOpenKeyExW (dir_key, L"OpenWithList", 0,
481		     KEY_READ, &reg_key) == ERROR_SUCCESS)
482    {
483      enumerate_open_with_list (reg_key, prognames);
484      RegCloseKey (reg_key);
485    }
486
487  if (RegOpenKeyExW (dir_key, L"OpenWithProgids", 0,
488		     KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS)
489    {
490      enumerate_open_with_progids (reg_key, progids);
491      RegCloseKey (reg_key);
492    }
493}
494
495static gboolean
496app_info_in_list (GAppInfo *info,
497                  GList    *list)
498{
499  while (list != NULL)
500    {
501      if (g_app_info_equal (info, list->data))
502	return TRUE;
503      list = list->next;
504    }
505  return FALSE;
506}
507
508GList *
509g_app_info_get_all_for_type (const char *content_type)
510{
511  GList *progids = NULL;
512  GList *prognames = NULL;
513  HKEY reg_key, sys_file_assoc_key, reg_key2;
514  wchar_t percieved_type[128];
515  DWORD nchars, key_type;
516  wchar_t *wc_key;
517  GList *l;
518  GList *infos;
519
520  wc_key = g_utf8_to_utf16 (content_type, -1, NULL, NULL, NULL);
521  if (RegOpenKeyExW (HKEY_CLASSES_ROOT, wc_key, 0,
522		     KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS)
523    {
524      enumerate_open_with_root (reg_key, &progids, &prognames);
525
526      nchars = sizeof (percieved_type) / sizeof(wchar_t);
527      if (RegQueryValueExW (reg_key, L"PerceivedType", 0,
528			    &key_type, (LPBYTE) percieved_type, &nchars) == ERROR_SUCCESS)
529	{
530	  if (key_type == REG_SZ &&
531	      RegOpenKeyExW (HKEY_CLASSES_ROOT, L"SystemFileAssociations", 0,
532			     KEY_QUERY_VALUE, &sys_file_assoc_key) == ERROR_SUCCESS)
533	    {
534	      if (RegOpenKeyExW (sys_file_assoc_key, percieved_type, 0,
535				 KEY_QUERY_VALUE, &reg_key2) == ERROR_SUCCESS)
536		{
537		  enumerate_open_with_root (reg_key2, &progids, &prognames);
538		  RegCloseKey (reg_key2);
539		}
540
541	      RegCloseKey (sys_file_assoc_key);
542	    }
543	}
544      RegCloseKey (reg_key);
545    }
546
547  if (RegOpenKeyExW (HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts", 0,
548		     KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS)
549    {
550      if (RegOpenKeyExW (reg_key, wc_key, 0,
551			 KEY_QUERY_VALUE, &reg_key2) == ERROR_SUCCESS)
552	{
553	  enumerate_open_with_root (reg_key2, &progids, &prognames);
554	  RegCloseKey (reg_key2);
555	}
556
557      RegCloseKey (reg_key);
558    }
559
560  infos = NULL;
561  for (l = prognames; 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, TRUE);
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 (prognames);
573
574  for (l = progids; l != NULL; l = l->next)
575    {
576      GAppInfo *info;
577
578      /* l->data ownership is taken */
579      info = g_desktop_app_info_new_from_id ((wchar_t *)l->data, FALSE);
580      if (app_info_in_list (info, infos))
581	g_object_unref (info);
582      else
583	infos = g_list_prepend (infos, info);
584    }
585  g_list_free (progids);
586
587  g_free (wc_key);
588  return g_list_reverse (infos);
589}
590
591GAppInfo *
592g_app_info_get_default_for_type (const char *content_type,
593				 gboolean    must_support_uris)
594{
595  wchar_t *wtype;
596  wchar_t buffer[1024];
597  DWORD buffer_size;
598
599  wtype = g_utf8_to_utf16 (content_type, -1, NULL, NULL, NULL);
600
601  /* Verify that we have some sort of app registered for this type */
602#ifdef AssocQueryString
603  buffer_size = 1024;
604  if (AssocQueryStringW (0,
605		  	 REAL_ASSOCSTR_COMMAND,
606			 wtype,
607			 NULL,
608			 buffer,
609			 &buffer_size) == S_OK)
610    /* Takes ownership of wtype */
611    return g_desktop_app_info_new_from_id (wtype, FALSE);
612#endif
613
614  g_free (wtype);
615  return NULL;
616}
617
618GAppInfo *
619g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
620{
621  /* TODO: Implement */
622  return NULL;
623}
624
625GList *
626g_app_info_get_all (void)
627{
628  DWORD index;
629  wchar_t name[256];
630  DWORD name_len;
631  HKEY reg_key;
632  GList *infos;
633  GAppInfo *info;
634
635  if (RegOpenKeyExW (HKEY_CLASSES_ROOT, L"Applications", 0,
636		     KEY_READ, &reg_key) != ERROR_SUCCESS)
637    return NULL;
638
639  infos = NULL;
640  index = 0;
641  name_len = 256;
642  while (RegEnumKeyExW (reg_key,
643		        index,
644		        name,
645		        &name_len,
646		        NULL,
647		        NULL,
648		        NULL,
649		        NULL) == ERROR_SUCCESS)
650    {
651      wchar_t *name_dup = g_memdup (name, (name_len+1)*2);
652      /* name_dup ownership is taken */
653      info = g_desktop_app_info_new_from_id (name_dup, TRUE);
654      infos = g_list_prepend (infos, info);
655
656      index++;
657      name_len = 256;
658    }
659
660  RegCloseKey (reg_key);
661
662  return g_list_reverse (infos);
663}
664
665void
666g_app_info_reset_type_associations (const char *content_type)
667{
668  /* nothing to do */
669}
670