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, ®_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, ®_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, ®_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, ®_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, ®_key) == ERROR_SUCCESS) 549 { 550 if (RegOpenKeyExW (reg_key, wc_key, 0, 551 KEY_QUERY_VALUE, ®_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, ®_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