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 (at) 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 54 static void g_win32_app_info_iface_init (GAppInfoIface *iface); 55 56 struct _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 67 G_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 72 static void 73 g_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 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 GIcon * 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_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 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_supports_files (GAppInfo *appinfo) 324 { 325 return TRUE; 326 } 327 328 static gboolean 329 g_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 340 static gboolean 341 g_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 351 static gboolean 352 g_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 362 GAppInfo * 363 g_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 375 static void 376 g_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 393 static void 394 enumerate_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 448 static void 449 enumerate_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 473 static void 474 enumerate_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 495 static gboolean 496 app_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 508 GList * 509 g_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 591 GAppInfo * 592 g_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 618 GAppInfo * 619 g_app_info_get_default_for_uri_scheme (const char *uri_scheme) 620 { 621 /* TODO: Implement */ 622 return NULL; 623 } 624 625 GList * 626 g_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 665 void 666 g_app_info_reset_type_associations (const char *content_type) 667 { 668 /* nothing to do */ 669 } 670