/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#include"mozilla/ArrayUtils.h"#include"mozilla/Preferences.h"#include"nsCOMPtr.h"#include"nsGNOMEShellService.h"#include"nsShellService.h"#include"nsIFile.h"#include"nsIProperties.h"#include"nsDirectoryServiceDefs.h"#include"prenv.h"#include"nsString.h"#include"nsIGIOService.h"#include"nsIGSettingsService.h"#include"nsIStringBundle.h"#include"nsServiceManagerUtils.h"#include"nsIImageLoadingContent.h"#include"imgIRequest.h"#include"imgIContainer.h"#include"mozilla/Components.h"#include"mozilla/GRefPtr.h"#include"mozilla/GUniquePtr.h"#include"mozilla/WidgetUtilsGtk.h"#include"mozilla/dom/Element.h"#include"nsImageToPixbuf.h"#include"nsXULAppAPI.h"#include"gfxPlatform.h"#include<glib.h>#include<gdk/gdk.h>#include<gdk-pixbuf/gdk-pixbuf.h>#include<stdlib.h>usingnamespacemozilla;structProtocolAssociation{constchar*name;boolessential;};structMimeTypeAssociation{constchar*mimeType;constchar*extensions;};staticconstProtocolAssociationappProtocols[]={// clang-format off{"http",true},{"https",true},{"chrome",false}// clang-format on};staticconstMimeTypeAssociationappTypes[]={// clang-format off{"text/html","htm html shtml"},{"application/xhtml+xml","xhtml xht"}// clang-format on};#define kDesktopBGSchema "org.gnome.desktop.background"_ns#define kDesktopColorGSKey "primary-color"_nsnsresultnsGNOMEShellService::Init(){nsresultrv;if(gfxPlatform::IsHeadless()){returnNS_ERROR_NOT_AVAILABLE;}// GSettings or GIO _must_ be available, or we do not allow// CreateInstance to succeed.nsCOMPtr<nsIGIOService>giovfs=do_GetService(NS_GIOSERVICE_CONTRACTID);nsCOMPtr<nsIGSettingsService>gsettings=do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);if(!giovfs&&!gsettings)returnNS_ERROR_NOT_AVAILABLE;#ifdef MOZ_ENABLE_DBUSif(widget::IsGnomeDesktopEnvironment()&&Preferences::GetBool("browser.gnome-search-provider.enabled",false)){mSearchProvider.Startup();}#endif// Check G_BROKEN_FILENAMES. If it's set, then filenames in glib use// the locale encoding. If it's not set, they use UTF-8.mUseLocaleFilenames=PR_GetEnv("G_BROKEN_FILENAMES")!=nullptr;if(GetAppPathFromLauncher())returnNS_OK;nsCOMPtr<nsIProperties>dirSvc(do_GetService("@mozilla.org/file/directory_service;1"));NS_ENSURE_TRUE(dirSvc,NS_ERROR_NOT_AVAILABLE);nsCOMPtr<nsIFile>appPath;rv=dirSvc->Get(XRE_EXECUTABLE_FILE,NS_GET_IID(nsIFile),getter_AddRefs(appPath));NS_ENSURE_SUCCESS(rv,rv);returnappPath->GetNativePath(mAppPath);}NS_IMPL_ISUPPORTS(nsGNOMEShellService,nsIGNOMEShellService,nsIShellService,nsIToolkitShellService)boolnsGNOMEShellService::GetAppPathFromLauncher(){gchar*tmp;constchar*launcher=PR_GetEnv("MOZ_APP_LAUNCHER");if(!launcher)returnfalse;if(g_path_is_absolute(launcher)){mAppPath=launcher;tmp=g_path_get_basename(launcher);gchar*fullpath=g_find_program_in_path(tmp);if(fullpath&&mAppPath.Equals(fullpath))mAppIsInPath=true;g_free(fullpath);}else{tmp=g_find_program_in_path(launcher);if(!tmp)returnfalse;mAppPath=tmp;mAppIsInPath=true;}g_free(tmp);returntrue;}boolnsGNOMEShellService::KeyMatchesAppName(constchar*aKeyValue)const{gchar*commandPath;if(mUseLocaleFilenames){gchar*nativePath=g_filename_from_utf8(aKeyValue,-1,nullptr,nullptr,nullptr);if(!nativePath){NS_ERROR("Error converting path to filesystem encoding");returnfalse;}commandPath=g_find_program_in_path(nativePath);g_free(nativePath);}else{commandPath=g_find_program_in_path(aKeyValue);}if(!commandPath)returnfalse;boolmatches=mAppPath.Equals(commandPath);g_free(commandPath);returnmatches;}boolnsGNOMEShellService::CheckHandlerMatchesAppName(constnsACString&handler)const{gintargc;gchar**argv;nsAutoCStringcommand(handler);// The string will be something of the form: [/path/to/]browser "%s"// We want to remove all of the parameters and get just the binary name.if(g_shell_parse_argv(command.get(),&argc,&argv,nullptr)&&argc>0){command.Assign(argv[0]);g_strfreev(argv);}if(!KeyMatchesAppName(command.get()))returnfalse;// the handler is set to another appreturntrue;}NS_IMETHODIMPnsGNOMEShellService::IsDefaultBrowser(boolaForAllTypes,bool*aIsDefaultBrowser){*aIsDefaultBrowser=false;if(widget::IsRunningUnderSnap()){constgchar*argv[]={"xdg-settings","check","default-web-browser",(MOZ_APP_NAME".desktop"),nullptr};GSpawnFlagsflags=static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH|G_SPAWN_STDERR_TO_DEV_NULL);gchar*output=nullptr;gintexit_status=0;if(!g_spawn_sync(nullptr,(gchar**)argv,nullptr,flags,nullptr,nullptr,&output,nullptr,&exit_status,nullptr)){returnNS_OK;}if(exit_status!=0){g_free(output);returnNS_OK;}if(strcmp(output,"yes\n")==0){*aIsDefaultBrowser=true;}g_free(output);returnNS_OK;}nsCOMPtr<nsIGIOService>giovfs=do_GetService(NS_GIOSERVICE_CONTRACTID);nsAutoCStringhandler;nsCOMPtr<nsIGIOMimeApp>gioApp;for(unsignedinti=0;i<std::size(appProtocols);++i){if(!appProtocols[i].essential)continue;if(!IsDefaultForSchemeHelper(nsDependentCString(appProtocols[i].name),giovfs)){returnNS_OK;}}*aIsDefaultBrowser=true;returnNS_OK;}boolnsGNOMEShellService::IsDefaultForSchemeHelper(constnsACString&aScheme,nsIGIOService*giovfs)const{nsCOMPtr<nsIGIOService>gioService;if(!giovfs){gioService=do_GetService(NS_GIOSERVICE_CONTRACTID);giovfs=gioService.get();}if(!giovfs){returnfalse;}nsCOMPtr<nsIGIOMimeApp>gioApp;nsCOMPtr<nsIHandlerApp>handlerApp;giovfs->GetAppForURIScheme(aScheme,getter_AddRefs(handlerApp));gioApp=do_QueryInterface(handlerApp);if(!gioApp){returnfalse;}nsAutoCStringhandler;gioApp->GetCommand(handler);returnCheckHandlerMatchesAppName(handler);}NS_IMETHODIMPnsGNOMEShellService::IsDefaultForScheme(constnsACString&aScheme,bool*aIsDefaultBrowser){*aIsDefaultBrowser=IsDefaultForSchemeHelper(aScheme,nullptr);returnNS_OK;}NS_IMETHODIMPnsGNOMEShellService::SetDefaultBrowser(boolaForAllUsers){#ifdef DEBUGif(aForAllUsers)NS_WARNING("Setting the default browser for all users is not yet supported");#endifif(widget::IsRunningUnderSnap()){constgchar*argv[]={"xdg-settings","set","default-web-browser",(MOZ_APP_NAME".desktop"),nullptr};GSpawnFlagsflags=static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH|G_SPAWN_STDOUT_TO_DEV_NULL|G_SPAWN_STDERR_TO_DEV_NULL);g_spawn_sync(nullptr,(gchar**)argv,nullptr,flags,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr);returnNS_OK;}nsCOMPtr<nsIGIOService>giovfs=do_GetService(NS_GIOSERVICE_CONTRACTID);if(giovfs){nsresultrv;nsCOMPtr<nsIStringBundleService>bundleService=components::StringBundle::Service(&rv);NS_ENSURE_SUCCESS(rv,rv);nsCOMPtr<nsIStringBundle>brandBundle;rv=bundleService->CreateBundle(BRAND_PROPERTIES,getter_AddRefs(brandBundle));NS_ENSURE_SUCCESS(rv,rv);nsAutoStringbrandShortName;brandBundle->GetStringFromName("brandShortName",brandShortName);// use brandShortName as the application id.NS_ConvertUTF16toUTF8id(brandShortName);nsCOMPtr<nsIGIOMimeApp>appInfo;rv=giovfs->FindAppFromCommand(mAppPath,getter_AddRefs(appInfo));if(NS_FAILED(rv)){// Application was not found in the list of installed applications// provided by OS. Fallback to create appInfo from command and name.rv=giovfs->CreateAppFromCommand(mAppPath,id,getter_AddRefs(appInfo));NS_ENSURE_SUCCESS(rv,rv);}// set handler for the protocolsfor(unsignedinti=0;i<std::size(appProtocols);++i){appInfo->SetAsDefaultForURIScheme(nsDependentCString(appProtocols[i].name));}// set handler for .html and xhtml files and MIME types:// Add mime types for html, xhtml extension and set app to just created// appinfo.for(unsignedinti=0;i<std::size(appTypes);++i){appInfo->SetAsDefaultForMimeType(nsDependentCString(appTypes[i].mimeType));appInfo->SetAsDefaultForFileExtensions(nsDependentCString(appTypes[i].extensions));}}nsCOMPtr<nsIPrefBranch>prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));if(prefs){(void)prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER,true);// Reset the number of times the dialog should be shown// before it is silenced.(void)prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT,0);}returnNS_OK;}NS_IMETHODIMPnsGNOMEShellService::GetCanSetDesktopBackground(bool*aResult){// setting desktop background is currently only supported// for Gnome or desktops using the same GSettings keysif(widget::IsGnomeDesktopEnvironment()){*aResult=true;returnNS_OK;}*aResult=!!getenv("GNOME_DESKTOP_SESSION_ID");returnNS_OK;}staticnsresultWriteImage(constnsCString&aPath,imgIContainer*aImage){RefPtr<GdkPixbuf>pixbuf=nsImageToPixbuf::ImageToPixbuf(aImage);if(!pixbuf){returnNS_ERROR_NOT_AVAILABLE;}gbooleanres=gdk_pixbuf_save(pixbuf,aPath.get(),"png",nullptr,nullptr);returnres?NS_OK:NS_ERROR_FAILURE;}NS_IMETHODIMPnsGNOMEShellService::SetDesktopBackground(dom::Element*aElement,int32_taPosition,constnsACString&aImageName){nsCOMPtr<nsIImageLoadingContent>imageContent=do_QueryInterface(aElement);if(!imageContent){returnNS_ERROR_FAILURE;}// get the image containernsCOMPtr<imgIRequest>request;imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,getter_AddRefs(request));if(!request){returnNS_ERROR_FAILURE;}nsCOMPtr<imgIContainer>container;request->GetImage(getter_AddRefs(container));if(!container){returnNS_ERROR_FAILURE;}// Set desktop wallpaper filling stylensAutoCStringoptions;if(aPosition==BACKGROUND_TILE)options.AssignLiteral("wallpaper");elseif(aPosition==BACKGROUND_STRETCH)options.AssignLiteral("stretched");elseif(aPosition==BACKGROUND_FILL)options.AssignLiteral("zoom");elseif(aPosition==BACKGROUND_FIT)options.AssignLiteral("scaled");elseif(aPosition==BACKGROUND_SPAN)options.AssignLiteral("spanned");elseoptions.AssignLiteral("centered");// Write the background file to the home directory.nsAutoCStringfilePath(PR_GetEnv("HOME"));nsAutoStringbrandName;// get the product brand name from localized stringsif(nsCOMPtr<nsIStringBundleService>bundleService=components::StringBundle::Service()){nsCOMPtr<nsIStringBundle>brandBundle;bundleService->CreateBundle(BRAND_PROPERTIES,getter_AddRefs(brandBundle));if(bundleService){brandBundle->GetStringFromName("brandShortName",brandName);}}// build the file namefilePath.Append('/');filePath.Append(NS_ConvertUTF16toUTF8(brandName));filePath.AppendLiteral("_wallpaper.png");// write the image to a file in the home dirMOZ_TRY(WriteImage(filePath,container));nsCOMPtr<nsIGSettingsService>gsettings=do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);if(!gsettings){returnNS_ERROR_FAILURE;}nsCOMPtr<nsIGSettingsCollection>backgroundSettings;gsettings->GetCollectionForSchema(kDesktopBGSchema,getter_AddRefs(backgroundSettings));if(!backgroundSettings){returnNS_ERROR_FAILURE;}GUniquePtr<gchar>fileURI(g_filename_to_uri(filePath.get(),nullptr,nullptr));if(!fileURI){returnNS_ERROR_FAILURE;}backgroundSettings->SetString("picture-options"_ns,options);backgroundSettings->SetString("picture-uri"_ns,nsDependentCString(fileURI.get()));backgroundSettings->SetString("picture-uri-dark"_ns,nsDependentCString(fileURI.get()));returnNS_OK;}#define COLOR_16_TO_8_BIT(_c) ((_c) >> 8)#define COLOR_8_TO_16_BIT(_c) ((_c) << 8 | (_c))NS_IMETHODIMPnsGNOMEShellService::GetDesktopBackgroundColor(uint32_t*aColor){nsCOMPtr<nsIGSettingsService>gsettings=do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);nsCOMPtr<nsIGSettingsCollection>background_settings;nsAutoCStringbackground;if(gsettings){gsettings->GetCollectionForSchema(kDesktopBGSchema,getter_AddRefs(background_settings));if(background_settings){background_settings->GetString(kDesktopColorGSKey,background);}}if(background.IsEmpty()){*aColor=0;returnNS_OK;}GdkColorcolor;gbooleansuccess=gdk_color_parse(background.get(),&color);NS_ENSURE_TRUE(success,NS_ERROR_FAILURE);*aColor=COLOR_16_TO_8_BIT(color.red)<<16|COLOR_16_TO_8_BIT(color.green)<<8|COLOR_16_TO_8_BIT(color.blue);returnNS_OK;}staticvoidColorToCString(uint32_taColor,nsCString&aResult){// The #rrrrggggbbbb format is used to match gdk_color_to_string()aResult.SetLength(13);char*buf=aResult.BeginWriting();if(!buf)return;uint16_tred=COLOR_8_TO_16_BIT((aColor>>16)&0xff);uint16_tgreen=COLOR_8_TO_16_BIT((aColor>>8)&0xff);uint16_tblue=COLOR_8_TO_16_BIT(aColor&0xff);snprintf(buf,14,"#%04x%04x%04x",red,green,blue);}NS_IMETHODIMPnsGNOMEShellService::SetDesktopBackgroundColor(uint32_taColor){NS_ASSERTION(aColor<=0xffffff,"aColor has extra bits");nsAutoCStringcolorString;ColorToCString(aColor,colorString);nsCOMPtr<nsIGSettingsService>gsettings=do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);if(gsettings){nsCOMPtr<nsIGSettingsCollection>background_settings;gsettings->GetCollectionForSchema(nsLiteralCString(kDesktopBGSchema),getter_AddRefs(background_settings));if(background_settings){background_settings->SetString(kDesktopColorGSKey,colorString);returnNS_OK;}}returnNS_ERROR_FAILURE;}