nsGNOMEShellService.cpp (15132B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "mozilla/Preferences.h" 7 #include "mozilla/widget/GSettings.h" 8 9 #include "nsCOMPtr.h" 10 #include "nsGNOMEShellService.h" 11 #include "nsShellService.h" 12 #include "nsIFile.h" 13 #include "nsIProperties.h" 14 #include "nsDirectoryServiceDefs.h" 15 #include "prenv.h" 16 #include "nsString.h" 17 #include "nsIGIOService.h" 18 #include "nsIStringBundle.h" 19 #include "nsServiceManagerUtils.h" 20 #include "nsIImageLoadingContent.h" 21 #include "imgIRequest.h" 22 #include "imgIContainer.h" 23 #include "mozilla/Components.h" 24 #include "mozilla/GRefPtr.h" 25 #include "mozilla/GUniquePtr.h" 26 #include "mozilla/WidgetUtilsGtk.h" 27 #include "mozilla/dom/Element.h" 28 #include "nsImageToPixbuf.h" 29 #include "nsXULAppAPI.h" 30 #include "gfxPlatform.h" 31 32 #include <glib.h> 33 #include <gdk/gdk.h> 34 #include <gdk-pixbuf/gdk-pixbuf.h> 35 #include <stdlib.h> 36 37 using namespace mozilla; 38 39 struct ProtocolAssociation { 40 const char* name; 41 bool essential; 42 }; 43 44 struct MimeTypeAssociation { 45 const char* mimeType; 46 const char* extensions; 47 }; 48 49 static const ProtocolAssociation appProtocols[] = { 50 // clang-format off 51 { "http", true }, 52 { "https", true }, 53 { "chrome", false } 54 // clang-format on 55 }; 56 57 static const MimeTypeAssociation appTypes[] = { 58 // clang-format off 59 { "text/html", "htm html shtml" }, 60 { "application/xhtml+xml", "xhtml xht" } 61 // clang-format on 62 }; 63 64 #define kDesktopBGSchema "org.gnome.desktop.background"_ns 65 #define kDesktopColorGSKey "primary-color"_ns 66 67 nsresult nsGNOMEShellService::Init() { 68 nsresult rv; 69 70 if (gfxPlatform::IsHeadless()) { 71 return NS_ERROR_NOT_AVAILABLE; 72 } 73 74 // GSettings or GIO _must_ be available, or we do not allow 75 // CreateInstance to succeed. 76 77 #ifdef MOZ_ENABLE_DBUS 78 if (widget::IsGnomeDesktopEnvironment() && 79 Preferences::GetBool("browser.gnome-search-provider.enabled", false)) { 80 mSearchProvider.Startup(); 81 } 82 #endif 83 84 // Check G_BROKEN_FILENAMES. If it's set, then filenames in glib use 85 // the locale encoding. If it's not set, they use UTF-8. 86 mUseLocaleFilenames = PR_GetEnv("G_BROKEN_FILENAMES") != nullptr; 87 88 if (GetAppPathFromLauncher()) return NS_OK; 89 90 nsCOMPtr<nsIProperties> dirSvc( 91 do_GetService("@mozilla.org/file/directory_service;1")); 92 NS_ENSURE_TRUE(dirSvc, NS_ERROR_NOT_AVAILABLE); 93 94 nsCOMPtr<nsIFile> appPath; 95 rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile), 96 getter_AddRefs(appPath)); 97 NS_ENSURE_SUCCESS(rv, rv); 98 99 return appPath->GetNativePath(mAppPath); 100 } 101 102 NS_IMPL_ISUPPORTS(nsGNOMEShellService, nsIGNOMEShellService, nsIShellService, 103 nsIToolkitShellService) 104 105 bool nsGNOMEShellService::GetAppPathFromLauncher() { 106 gchar* tmp; 107 108 const char* launcher = PR_GetEnv("MOZ_APP_LAUNCHER"); 109 if (!launcher) return false; 110 111 if (g_path_is_absolute(launcher)) { 112 mAppPath = launcher; 113 tmp = g_path_get_basename(launcher); 114 gchar* fullpath = g_find_program_in_path(tmp); 115 if (fullpath && mAppPath.Equals(fullpath)) mAppIsInPath = true; 116 g_free(fullpath); 117 } else { 118 tmp = g_find_program_in_path(launcher); 119 if (!tmp) return false; 120 mAppPath = tmp; 121 mAppIsInPath = true; 122 } 123 124 g_free(tmp); 125 return true; 126 } 127 128 bool nsGNOMEShellService::KeyMatchesAppName(const char* aKeyValue) const { 129 gchar* commandPath; 130 if (mUseLocaleFilenames) { 131 gchar* nativePath = 132 g_filename_from_utf8(aKeyValue, -1, nullptr, nullptr, nullptr); 133 if (!nativePath) { 134 NS_ERROR("Error converting path to filesystem encoding"); 135 return false; 136 } 137 138 commandPath = g_find_program_in_path(nativePath); 139 g_free(nativePath); 140 } else { 141 commandPath = g_find_program_in_path(aKeyValue); 142 } 143 144 if (!commandPath) return false; 145 146 bool matches = mAppPath.Equals(commandPath); 147 g_free(commandPath); 148 return matches; 149 } 150 151 bool nsGNOMEShellService::CheckHandlerMatchesAppName( 152 const nsACString& handler) const { 153 gint argc; 154 gchar** argv; 155 nsAutoCString command(handler); 156 157 // The string will be something of the form: [/path/to/]browser "%s" 158 // We want to remove all of the parameters and get just the binary name. 159 160 if (g_shell_parse_argv(command.get(), &argc, &argv, nullptr) && argc > 0) { 161 command.Assign(argv[0]); 162 g_strfreev(argv); 163 } 164 165 if (!KeyMatchesAppName(command.get())) 166 return false; // the handler is set to another app 167 168 return true; 169 } 170 171 NS_IMETHODIMP 172 nsGNOMEShellService::IsDefaultBrowser(bool aForAllTypes, 173 bool* aIsDefaultBrowser) { 174 *aIsDefaultBrowser = false; 175 176 if (widget::IsRunningUnderSnap()) { 177 const gchar* argv[] = {"xdg-settings", "check", "default-web-browser", 178 (MOZ_APP_NAME ".desktop"), nullptr}; 179 GSpawnFlags flags = static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH | 180 G_SPAWN_STDERR_TO_DEV_NULL); 181 gchar* output = nullptr; 182 gint exit_status = 0; 183 if (!g_spawn_sync(nullptr, (gchar**)argv, nullptr, flags, nullptr, nullptr, 184 &output, nullptr, &exit_status, nullptr)) { 185 return NS_OK; 186 } 187 if (exit_status != 0) { 188 g_free(output); 189 return NS_OK; 190 } 191 if (strcmp(output, "yes\n") == 0) { 192 *aIsDefaultBrowser = true; 193 } 194 g_free(output); 195 return NS_OK; 196 } 197 198 nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); 199 nsAutoCString handler; 200 nsCOMPtr<nsIGIOMimeApp> gioApp; 201 202 for (unsigned int i = 0; i < std::size(appProtocols); ++i) { 203 if (!appProtocols[i].essential) continue; 204 205 if (!IsDefaultForSchemeHelper(nsDependentCString(appProtocols[i].name), 206 giovfs)) { 207 return NS_OK; 208 } 209 } 210 211 *aIsDefaultBrowser = true; 212 213 return NS_OK; 214 } 215 216 bool nsGNOMEShellService::IsDefaultForSchemeHelper( 217 const nsACString& aScheme, nsIGIOService* giovfs) const { 218 nsCOMPtr<nsIGIOService> gioService; 219 if (!giovfs) { 220 gioService = do_GetService(NS_GIOSERVICE_CONTRACTID); 221 giovfs = gioService.get(); 222 } 223 224 if (!giovfs) { 225 return false; 226 } 227 228 nsCOMPtr<nsIGIOMimeApp> gioApp; 229 nsCOMPtr<nsIHandlerApp> handlerApp; 230 giovfs->GetAppForURIScheme(aScheme, getter_AddRefs(handlerApp)); 231 gioApp = do_QueryInterface(handlerApp); 232 if (!gioApp) { 233 return false; 234 } 235 236 nsAutoCString handler; 237 gioApp->GetCommand(handler); 238 return CheckHandlerMatchesAppName(handler); 239 } 240 241 NS_IMETHODIMP 242 nsGNOMEShellService::IsDefaultForScheme(const nsACString& aScheme, 243 bool* aIsDefaultBrowser) { 244 *aIsDefaultBrowser = IsDefaultForSchemeHelper(aScheme, nullptr); 245 return NS_OK; 246 } 247 248 NS_IMETHODIMP 249 nsGNOMEShellService::SetDefaultBrowser(bool aForAllUsers) { 250 #ifdef DEBUG 251 if (aForAllUsers) 252 NS_WARNING( 253 "Setting the default browser for all users is not yet supported"); 254 #endif 255 256 if (widget::IsRunningUnderSnap()) { 257 const gchar* argv[] = {"xdg-settings", "set", "default-web-browser", 258 (MOZ_APP_NAME ".desktop"), nullptr}; 259 GSpawnFlags flags = static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH | 260 G_SPAWN_STDOUT_TO_DEV_NULL | 261 G_SPAWN_STDERR_TO_DEV_NULL); 262 g_spawn_sync(nullptr, (gchar**)argv, nullptr, flags, nullptr, nullptr, 263 nullptr, nullptr, nullptr, nullptr); 264 return NS_OK; 265 } 266 267 nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); 268 if (giovfs) { 269 nsresult rv; 270 nsCOMPtr<nsIStringBundleService> bundleService = 271 components::StringBundle::Service(&rv); 272 NS_ENSURE_SUCCESS(rv, rv); 273 274 nsCOMPtr<nsIStringBundle> brandBundle; 275 rv = bundleService->CreateBundle(BRAND_PROPERTIES, 276 getter_AddRefs(brandBundle)); 277 NS_ENSURE_SUCCESS(rv, rv); 278 279 nsAutoString brandShortName; 280 brandBundle->GetStringFromName("brandShortName", brandShortName); 281 282 // use brandShortName as the application id. 283 NS_ConvertUTF16toUTF8 id(brandShortName); 284 nsCOMPtr<nsIGIOMimeApp> appInfo; 285 rv = giovfs->FindAppFromCommand(mAppPath, getter_AddRefs(appInfo)); 286 if (NS_FAILED(rv)) { 287 // Application was not found in the list of installed applications 288 // provided by OS. Fallback to create appInfo from command and name. 289 rv = giovfs->CreateAppFromCommand(mAppPath, id, getter_AddRefs(appInfo)); 290 NS_ENSURE_SUCCESS(rv, rv); 291 } 292 293 // set handler for the protocols 294 for (unsigned int i = 0; i < std::size(appProtocols); ++i) { 295 appInfo->SetAsDefaultForURIScheme( 296 nsDependentCString(appProtocols[i].name)); 297 } 298 299 // set handler for .html and xhtml files and MIME types: 300 // Add mime types for html, xhtml extension and set app to just created 301 // appinfo. 302 for (unsigned int i = 0; i < std::size(appTypes); ++i) { 303 appInfo->SetAsDefaultForMimeType( 304 nsDependentCString(appTypes[i].mimeType)); 305 appInfo->SetAsDefaultForFileExtensions( 306 nsDependentCString(appTypes[i].extensions)); 307 } 308 } 309 310 nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); 311 if (prefs) { 312 (void)prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true); 313 // Reset the number of times the dialog should be shown 314 // before it is silenced. 315 (void)prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0); 316 } 317 318 return NS_OK; 319 } 320 321 NS_IMETHODIMP 322 nsGNOMEShellService::GetCanSetDesktopBackground(bool* aResult) { 323 // setting desktop background is currently only supported 324 // for Gnome or desktops using the same GSettings keys 325 if (widget::IsGnomeDesktopEnvironment()) { 326 *aResult = true; 327 return NS_OK; 328 } 329 330 *aResult = !!getenv("GNOME_DESKTOP_SESSION_ID"); 331 return NS_OK; 332 } 333 334 static nsresult WriteImage(const nsCString& aPath, imgIContainer* aImage) { 335 RefPtr<GdkPixbuf> pixbuf = nsImageToPixbuf::ImageToPixbuf(aImage); 336 if (!pixbuf) { 337 return NS_ERROR_NOT_AVAILABLE; 338 } 339 340 gboolean res = gdk_pixbuf_save(pixbuf, aPath.get(), "png", nullptr, nullptr); 341 return res ? NS_OK : NS_ERROR_FAILURE; 342 } 343 344 NS_IMETHODIMP 345 nsGNOMEShellService::SetDesktopBackground(dom::Element* aElement, 346 int32_t aPosition, 347 const nsACString& aImageName) { 348 nsCOMPtr<nsIImageLoadingContent> imageContent = do_QueryInterface(aElement); 349 if (!imageContent) { 350 return NS_ERROR_FAILURE; 351 } 352 353 // get the image container 354 nsCOMPtr<imgIRequest> request; 355 imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, 356 getter_AddRefs(request)); 357 if (!request) { 358 return NS_ERROR_FAILURE; 359 } 360 nsCOMPtr<imgIContainer> container; 361 request->GetImage(getter_AddRefs(container)); 362 if (!container) { 363 return NS_ERROR_FAILURE; 364 } 365 366 // Set desktop wallpaper filling style 367 nsAutoCString options; 368 if (aPosition == BACKGROUND_TILE) 369 options.AssignLiteral("wallpaper"); 370 else if (aPosition == BACKGROUND_STRETCH) 371 options.AssignLiteral("stretched"); 372 else if (aPosition == BACKGROUND_FILL) 373 options.AssignLiteral("zoom"); 374 else if (aPosition == BACKGROUND_FIT) 375 options.AssignLiteral("scaled"); 376 else if (aPosition == BACKGROUND_SPAN) 377 options.AssignLiteral("spanned"); 378 else 379 options.AssignLiteral("centered"); 380 381 // Write the background file to the home directory. 382 nsAutoCString filePath(PR_GetEnv("HOME")); 383 nsAutoString brandName; 384 385 // get the product brand name from localized strings 386 if (nsCOMPtr<nsIStringBundleService> bundleService = 387 components::StringBundle::Service()) { 388 nsCOMPtr<nsIStringBundle> brandBundle; 389 bundleService->CreateBundle(BRAND_PROPERTIES, getter_AddRefs(brandBundle)); 390 if (bundleService) { 391 brandBundle->GetStringFromName("brandShortName", brandName); 392 } 393 } 394 395 // build the file name 396 filePath.Append('/'); 397 filePath.Append(NS_ConvertUTF16toUTF8(brandName)); 398 filePath.AppendLiteral("_wallpaper.png"); 399 400 // write the image to a file in the home dir 401 MOZ_TRY(WriteImage(filePath, container)); 402 403 widget::GSettings::Collection bgSettings(kDesktopBGSchema); 404 if (!bgSettings) { 405 return NS_ERROR_FAILURE; 406 } 407 408 GUniquePtr<gchar> fileURI( 409 g_filename_to_uri(filePath.get(), nullptr, nullptr)); 410 if (!fileURI) { 411 return NS_ERROR_FAILURE; 412 } 413 414 bgSettings.SetString("picture-options"_ns, options); 415 bgSettings.SetString("picture-uri"_ns, nsDependentCString(fileURI.get())); 416 bgSettings.SetString("picture-uri-dark"_ns, 417 nsDependentCString(fileURI.get())); 418 return NS_OK; 419 } 420 421 #define COLOR_16_TO_8_BIT(_c) ((_c) >> 8) 422 #define COLOR_8_TO_16_BIT(_c) ((_c) << 8 | (_c)) 423 424 NS_IMETHODIMP 425 nsGNOMEShellService::GetDesktopBackgroundColor(uint32_t* aColor) { 426 nsAutoCString background; 427 widget::GSettings::GetString(kDesktopBGSchema, kDesktopColorGSKey, 428 background); 429 if (background.IsEmpty()) { 430 *aColor = 0; 431 return NS_OK; 432 } 433 434 GdkColor color; 435 gboolean success = gdk_color_parse(background.get(), &color); 436 437 NS_ENSURE_TRUE(success, NS_ERROR_FAILURE); 438 439 *aColor = COLOR_16_TO_8_BIT(color.red) << 16 | 440 COLOR_16_TO_8_BIT(color.green) << 8 | COLOR_16_TO_8_BIT(color.blue); 441 return NS_OK; 442 } 443 444 static void ColorToCString(uint32_t aColor, nsCString& aResult) { 445 // The #rrrrggggbbbb format is used to match gdk_color_to_string() 446 aResult.SetLength(13); 447 char* buf = aResult.BeginWriting(); 448 if (!buf) return; 449 450 uint16_t red = COLOR_8_TO_16_BIT((aColor >> 16) & 0xff); 451 uint16_t green = COLOR_8_TO_16_BIT((aColor >> 8) & 0xff); 452 uint16_t blue = COLOR_8_TO_16_BIT(aColor & 0xff); 453 454 snprintf(buf, 14, "#%04x%04x%04x", red, green, blue); 455 } 456 457 NS_IMETHODIMP 458 nsGNOMEShellService::SetDesktopBackgroundColor(uint32_t aColor) { 459 NS_ASSERTION(aColor <= 0xffffff, "aColor has extra bits"); 460 nsAutoCString colorString; 461 ColorToCString(aColor, colorString); 462 463 widget::GSettings::Collection bgSettings(kDesktopBGSchema); 464 if (bgSettings) { 465 bgSettings.SetString(kDesktopColorGSKey, colorString); 466 return NS_OK; 467 } 468 469 return NS_ERROR_FAILURE; 470 } 471 472 NS_IMETHODIMP 473 nsGNOMEShellService::GetGSettingsString(const nsACString& aSchema, 474 const nsACString& aKey, 475 nsACString& aResult) { 476 widget::GSettings::GetString(PromiseFlatCString(aSchema), 477 PromiseFlatCString(aKey), aResult); 478 return NS_OK; 479 } 480 481 NS_IMETHODIMP 482 nsGNOMEShellService::SetGSettingsString(const nsACString& aSchema, 483 const nsACString& aKey, 484 const nsACString& aValue) { 485 widget::GSettings::Collection settings(PromiseFlatCString(aSchema)); 486 if (!settings) { 487 return NS_ERROR_FAILURE; 488 } 489 if (!settings.SetString(PromiseFlatCString(aKey), 490 PromiseFlatCString(aValue))) { 491 return NS_ERROR_FAILURE; 492 } 493 return NS_OK; 494 }