blob: f373076973c9032c0047eeba50b910d72470c833 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/web_applications/web_app_mac.h"
#import <Cocoa/Cocoa.h>
#include "base/file_util.h"
#include "base/mac/bundle_locations.h"
#include "base/mac/foundation_util.h"
#include "base/mac/mac_util.h"
#include "base/memory/scoped_nsobject.h"
#include "base/scoped_temp_dir.h"
#include "base/sys_string_conversions.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/common/chrome_paths_internal.h"
#include "chrome/common/mac/app_mode_common.h"
#include "content/public/browser/browser_thread.h"
#include "grit/chromium_strings.h"
#include "skia/ext/skia_utils_mac.h"
#include "third_party/icon_family/IconFamily.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/gfx/image/image_skia.h"
namespace {
// Creates a NSBitmapImageRep from |bitmap|.
NSBitmapImageRep* SkBitmapToImageRep(const SkBitmap& bitmap) {
base::mac::ScopedCFTypeRef<CGColorSpaceRef> color_space(
CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
NSImage* image = gfx::SkBitmapToNSImageWithColorSpace(
bitmap, color_space.get());
return base::mac::ObjCCast<NSBitmapImageRep>(
[[image representations] lastObject]);
}
// Adds |image_rep| to |icon_family|. Returns true on success, false on failure.
bool AddBitmapImageRepToIconFamily(IconFamily* icon_family,
NSBitmapImageRep* image_rep) {
NSSize size = [image_rep size];
if (size.width != size.height)
return false;
switch (static_cast<int>(size.width)) {
case 512:
return [icon_family setIconFamilyElement:kIconServices512PixelDataARGB
fromBitmapImageRep:image_rep];
case 256:
return [icon_family setIconFamilyElement:kIconServices256PixelDataARGB
fromBitmapImageRep:image_rep];
case 128:
return [icon_family setIconFamilyElement:kThumbnail32BitData
fromBitmapImageRep:image_rep] &&
[icon_family setIconFamilyElement:kThumbnail8BitMask
fromBitmapImageRep:image_rep];
case 32:
return [icon_family setIconFamilyElement:kLarge32BitData
fromBitmapImageRep:image_rep] &&
[icon_family setIconFamilyElement:kLarge8BitData
fromBitmapImageRep:image_rep] &&
[icon_family setIconFamilyElement:kLarge8BitMask
fromBitmapImageRep:image_rep] &&
[icon_family setIconFamilyElement:kLarge1BitMask
fromBitmapImageRep:image_rep];
case 16:
return [icon_family setIconFamilyElement:kSmall32BitData
fromBitmapImageRep:image_rep] &&
[icon_family setIconFamilyElement:kSmall8BitData
fromBitmapImageRep:image_rep] &&
[icon_family setIconFamilyElement:kSmall8BitMask
fromBitmapImageRep:image_rep] &&
[icon_family setIconFamilyElement:kSmall1BitMask
fromBitmapImageRep:image_rep];
default:
return false;
}
}
} // namespace
namespace web_app {
WebAppShortcutCreator::WebAppShortcutCreator(
const FilePath& user_data_dir,
const ShellIntegration::ShortcutInfo& shortcut_info,
const string16& chrome_bundle_id)
: user_data_dir_(user_data_dir),
info_(shortcut_info),
chrome_bundle_id_(chrome_bundle_id) {
}
WebAppShortcutCreator::~WebAppShortcutCreator() {
}
bool WebAppShortcutCreator::CreateShortcut() {
FilePath app_name = internals::GetSanitizedFileName(info_.title);
FilePath app_file_name = app_name.ReplaceExtension("app");
ScopedTempDir scoped_temp_dir;
if (!scoped_temp_dir.CreateUniqueTempDir())
return false;
FilePath staging_path = scoped_temp_dir.path().Append(app_file_name);
// Update the app's plist and icon in a temp directory. This works around
// a Finder bug where the app's icon doesn't properly update.
if (!file_util::CopyDirectory(GetAppLoaderPath(), staging_path, true)) {
LOG(ERROR) << "Copying app to staging path: " << staging_path.value()
<< " failed";
return false;
}
if (!UpdatePlist(staging_path))
return false;
if (!UpdateIcon(staging_path))
return false;
FilePath dst_path = GetDestinationPath(app_file_name);
if (!file_util::CopyDirectory(staging_path, dst_path, true)) {
LOG(ERROR) << "Copying app to dst path: " << dst_path.value() << " failed";
return false;
}
RevealGeneratedBundleInFinder(dst_path);
return true;
}
FilePath WebAppShortcutCreator::GetAppLoaderPath() const {
return base::mac::PathForFrameworkBundleResource(
base::mac::NSToCFCast(@"app_mode_loader.app"));
}
FilePath WebAppShortcutCreator::GetDestinationPath(
const FilePath& app_file_name) const {
FilePath path;
if (base::mac::GetLocalDirectory(NSApplicationDirectory, &path) &&
file_util::PathIsWritable(path)) {
return path;
}
if (base::mac::GetUserDirectory(NSApplicationDirectory, &path))
return path;
return FilePath();
}
bool WebAppShortcutCreator::UpdatePlist(const FilePath& app_path) const {
NSString* plist_path = base::mac::FilePathToNSString(
app_path.Append("Contents").Append("Info.plist"));
NSString* extension_id = base::SysUTF8ToNSString(info_.extension_id);
NSString* extension_title = base::SysUTF16ToNSString(info_.title);
NSString* extension_url = base::SysUTF8ToNSString(info_.url.spec());
NSString* chrome_bundle_id = base::SysUTF16ToNSString(chrome_bundle_id_);
NSDictionary* replacement_dict =
[NSDictionary dictionaryWithObjectsAndKeys:
extension_id, app_mode::kShortcutIdPlaceholder,
extension_title, app_mode::kShortcutNamePlaceholder,
extension_url, app_mode::kShortcutURLPlaceholder,
chrome_bundle_id, app_mode::kShortcutBrowserBundleIDPlaceholder,
nil];
NSMutableDictionary* plist =
[NSMutableDictionary dictionaryWithContentsOfFile:plist_path];
NSArray* keys = [plist allKeys];
// 1. Fill in variables.
for (id key in keys) {
NSString* value = [plist valueForKey:key];
if (![value isKindOfClass:[NSString class]] || [value length] < 2)
continue;
// Remove leading and trailing '@'s.
NSString* variable =
[value substringWithRange:NSMakeRange(1, [value length] - 2)];
NSString* substitution = [replacement_dict valueForKey:variable];
if (substitution)
[plist setObject:substitution forKey:key];
}
// 2. Fill in other values.
[plist setObject:GetBundleIdentifier(plist)
forKey:base::mac::CFToNSCast(kCFBundleIdentifierKey)];
[plist setObject:base::mac::FilePathToNSString(user_data_dir_)
forKey:app_mode::kCrAppModeUserDataDirKey];
[plist setObject:base::mac::FilePathToNSString(info_.extension_path)
forKey:app_mode::kCrAppModeExtensionPathKey];
return [plist writeToFile:plist_path atomically:YES];
}
bool WebAppShortcutCreator::UpdateIcon(const FilePath& app_path) const {
if (info_.favicon.IsEmpty())
return true;
scoped_nsobject<IconFamily> icon_family([[IconFamily alloc] init]);
bool image_added = false;
info_.favicon.ToImageSkia()->EnsureRepsForSupportedScaleFactors();
std::vector<gfx::ImageSkiaRep> image_reps =
info_.favicon.ToImageSkia()->image_reps();
for (size_t i = 0; i < image_reps.size(); ++i) {
NSBitmapImageRep* image_rep = SkBitmapToImageRep(
image_reps[i].sk_bitmap());
if (!image_rep)
continue;
// Missing an icon size is not fatal so don't fail if adding the bitmap
// doesn't work.
if (!AddBitmapImageRepToIconFamily(icon_family, image_rep))
continue;
image_added = true;
}
if (!image_added)
return false;
FilePath resources_path = app_path.Append("Contents").Append("Resources");
if (!file_util::CreateDirectory(resources_path))
return false;
FilePath icon_path = resources_path.Append("app.icns");
return [icon_family writeToFile:base::mac::FilePathToNSString(icon_path)];
}
NSString* WebAppShortcutCreator::GetBundleIdentifier(NSDictionary* plist) const
{
NSString* bundle_id_template =
base::mac::ObjCCast<NSString>(
[plist objectForKey:base::mac::CFToNSCast(kCFBundleIdentifierKey)]);
NSString* extension_id = base::SysUTF8ToNSString(info_.extension_id);
NSString* placeholder =
[NSString stringWithFormat:@"@%@@", app_mode::kShortcutIdPlaceholder];
NSString* bundle_id =
[bundle_id_template
stringByReplacingOccurrencesOfString:placeholder
withString:extension_id];
return bundle_id;
}
void WebAppShortcutCreator::RevealGeneratedBundleInFinder(
const FilePath& generated_bundle) const {
[[NSWorkspace sharedWorkspace]
selectFile:base::mac::FilePathToNSString(generated_bundle)
inFileViewerRootedAtPath:nil];
}
} // namespace
namespace web_app {
namespace internals {
bool CreatePlatformShortcuts(
const FilePath& web_app_path,
const ShellIntegration::ShortcutInfo& shortcut_info) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
string16 bundle_id = UTF8ToUTF16(base::mac::BaseBundleID());
WebAppShortcutCreator shortcut_creator(web_app_path, shortcut_info,
bundle_id);
return shortcut_creator.CreateShortcut();
}
void DeletePlatformShortcuts(
const FilePath& web_app_path,
const ShellIntegration::ShortcutInfo& shortcut_info) {
// TODO(benwells): Implement this when shortcuts / weblings are enabled on
// mac.
}
} // namespace internals
} // namespace web_app