1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
|
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/web_applications/os_integration/mac/apps_folder_support.h"
#import <Cocoa/Cocoa.h>
#import "base/apple/foundation_util.h"
#include "base/check_is_test.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/sys_string_conversions.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/version_info/channel.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/browser/shortcuts/platform_util_mac.h"
#include "chrome/browser/web_applications/os_integration/mac/icon_utils.h"
#include "chrome/browser/web_applications/os_integration/os_integration_test_override.h"
#include "chrome/common/channel_info.h"
#include "chrome/grit/chrome_unscaled_resources.h"
#include "content/public/browser/browser_thread.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image.h"
namespace web_app {
namespace {
// Set to true the first time the localized name of the chrome apps dir has been
// updated sucessfully, as this only needs to be done once.
bool g_have_localized_app_dir_name = false;
base::FilePath GetLocalizableAppShortcutsSubdirName() {
static const char kChromiumAppDirName[] = "Chromium Apps.localized";
static const char kChromeAppDirName[] = "Chrome Apps.localized";
static const char kChromeCanaryAppDirName[] = "Chrome Canary Apps.localized";
switch (chrome::GetChannel()) {
case version_info::Channel::UNKNOWN:
return base::FilePath(kChromiumAppDirName);
case version_info::Channel::CANARY:
return base::FilePath(kChromeCanaryAppDirName);
default:
return base::FilePath(kChromeAppDirName);
}
}
base::FilePath GetWritableApplicationsDirectory() {
base::FilePath path;
if (base::apple::GetUserDirectory(NSApplicationDirectory, &path)) {
if (!base::DirectoryExists(path)) {
if (!base::CreateDirectory(path)) {
return base::FilePath();
}
// Create a zero-byte ".localized" file to inherit localizations from
// macOS for folders that have special meaning.
base::WriteFile(path.Append(".localized"), "");
}
return base::PathIsWritable(path) ? path : base::FilePath();
}
return base::FilePath();
}
base::FilePath GetChromeAppsFolderImpl() {
scoped_refptr<OsIntegrationTestOverride> os_override =
OsIntegrationTestOverride::Get();
if (os_override) {
CHECK_IS_TEST();
if (os_override->IsChromeAppsValid()) {
return os_override->chrome_apps_folder();
}
return base::FilePath();
}
base::FilePath path = GetWritableApplicationsDirectory();
if (path.empty()) {
return path;
}
return path.Append(GetLocalizableAppShortcutsSubdirName());
}
// Helper function to extract the single NSImageRep held in a resource bundle
// image.
NSImageRep* ImageRepForGFXImage(const gfx::Image& image) {
NSArray* image_reps = image.AsNSImage().representations;
DCHECK_EQ(1u, image_reps.count);
return image_reps[0];
}
using ResourceIDToImage = std::map<int, NSImageRep*>;
// Generates a map of NSImageReps used by SetWorkspaceIconOnWorkerThread and
// passes it to |io_task|. Since ui::ResourceBundle can only be used on UI
// thread, this function also needs to run on UI thread, and the gfx::Images
// need to be converted to NSImageReps on the UI thread due to non-thread-safety
// of gfx::Image.
ResourceIDToImage GetImageResourcesOnUIThread() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
ResourceIDToImage result;
// These resource ID should match to the ones used by
// SetWorkspaceIconOnWorkerThread below.
for (int id : {IDR_APPS_FOLDER_16, IDR_APPS_FOLDER_32,
IDR_APPS_FOLDER_OVERLAY_128, IDR_APPS_FOLDER_OVERLAY_512}) {
gfx::Image image = resource_bundle.GetNativeImageNamed(id);
result[id] = ImageRepForGFXImage(image);
}
return result;
}
void SetWorkspaceIconOnWorkerThread(const base::FilePath& apps_directory,
const ResourceIDToImage& images) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
NSImage* folder_icon_image = [[NSImage alloc] init];
// Use complete assets for the small icon sizes. -[NSWorkspace setIcon:] has a
// bug when dealing with named NSImages where it incorrectly handles alpha
// premultiplication. This is most noticeable with small assets since the 1px
// border is a much larger component of the small icons.
// See http://crbug.com/305373 for details.
for (int id : {IDR_APPS_FOLDER_16, IDR_APPS_FOLDER_32}) {
const auto& found = images.find(id);
DCHECK(found != images.end());
[folder_icon_image addRepresentation:found->second];
}
// Brand larger folder assets with an embossed app launcher logo to
// conserve distro size and for better consistency with changing hue
// across macOS versions. The folder is textured, so compresses poorly
// without this.
NSImage* base_image = [NSImage imageNamed:NSImageNameFolder];
for (int id : {IDR_APPS_FOLDER_OVERLAY_128, IDR_APPS_FOLDER_OVERLAY_512}) {
const auto& found = images.find(id);
DCHECK(found != images.end());
NSImageRep* with_overlay = OverlayImageRep(base_image, found->second);
DCHECK(with_overlay);
if (with_overlay) {
[folder_icon_image addRepresentation:with_overlay];
}
}
shortcuts::SetIconForFile(folder_icon_image, apps_directory,
base::DoNothing());
}
// Adds a localized strings file for the Chrome Apps directory using the current
// locale. macOS will use this for the display name.
// + Chrome Apps.localized (|apps_directory|)
// | + .localized
// | | en.strings
// | | de.strings
bool UpdateAppShortcutsSubdirLocalizedName(
const base::FilePath& apps_directory) {
base::FilePath localized = apps_directory.Append(".localized");
if (!base::CreateDirectory(localized)) {
return false;
}
base::FilePath directory_name = apps_directory.BaseName().RemoveExtension();
std::u16string localized_name =
shell_integration::GetAppShortcutsSubdirName();
NSDictionary* strings_dict = @{
base::apple::FilePathToNSString(directory_name) :
base::SysUTF16ToNSString(localized_name)
};
std::string locale = l10n_util::NormalizeLocale(
l10n_util::GetApplicationLocale(std::string()));
NSURL* strings_url =
base::apple::FilePathToNSURL(localized.Append(locale + ".strings"));
[strings_dict writeToURL:strings_url error:nil];
content::GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&GetImageResourcesOnUIThread),
base::BindOnce(&SetWorkspaceIconOnWorkerThread, apps_directory));
return true;
}
} // namespace
base::FilePath GetChromeAppsFolder() {
base::FilePath path = GetChromeAppsFolderImpl();
if (path.empty()) {
return path;
}
// Only set folder icons and a localized name once, as nothing should be
// changing the folder icon and name.
if (!g_have_localized_app_dir_name) {
g_have_localized_app_dir_name = UpdateAppShortcutsSubdirLocalizedName(path);
}
if (!g_have_localized_app_dir_name) {
LOG(ERROR) << "Failed to localize " << path;
}
return path;
}
void ResetHaveLocalizedAppDirNameForTesting() {
g_have_localized_app_dir_name = false;
}
} // namespace web_app
|