blob: 2f07ba9bca989543dde90b8912873dce53760fa1 [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import "base/mac_util.h"
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
#include <launch.h>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include "base/const.h"
#include "base/logging.h"
#include "base/scoped_cftyperef.h"
#include "base/singleton.h"
#include "base/util.h"
namespace mozc {
namespace {
const char kServerDirectory[] =
"/Library/Input Methods/" kProductPrefix ".app/Contents/Resources";
const unsigned char kPrelauncherPath[] =
"/Library/Input Methods/" kProductPrefix ".app/Contents/Resources/"
kProductPrefix "Prelauncher.app";
#ifdef GOOGLE_JAPANESE_INPUT_BUILD
const char kProjectPrefix[] =
"com.google.inputmethod.Japanese.";
#elif defined(MOZC_BUILD)
const char kProjectPrefix[] =
"org.mozc.inputmethod.Japanese.";
#else
#error Unknown branding
#endif
// Returns the reference of prelauncher login item.
// If the prelauncher login item does not exist this function returns NULL.
// Otherwise you must release the reference.
LSSharedFileListItemRef GetPrelauncherLoginItem() {
LSSharedFileListItemRef prelauncher_item = NULL;
scoped_cftyperef<CFURLRef> url(
CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault, kPrelauncherPath,
strlen((const char *)kPrelauncherPath), true));
if (!url.get()) {
LOG(ERROR) << "CFURLCreateFromFileSystemRepresentation error:"
<< " Cannot create CFURL object.";
return NULL;
}
scoped_cftyperef<LSSharedFileListRef> login_items(
LSSharedFileListCreate(
kCFAllocatorDefault, kLSSharedFileListSessionLoginItems, NULL));
if (!login_items.get()) {
LOG(ERROR) << "LSSharedFileListCreate error: Cannot get the login items.";
return NULL;
}
scoped_cftyperef<CFArrayRef> login_items_array(
LSSharedFileListCopySnapshot(login_items.get(), NULL));
if (!login_items_array.get()) {
LOG(ERROR) << "LSSharedFileListCopySnapshot error:"
<< " Cannot get the login items.";
return NULL;
}
for(CFIndex i = 0; i < CFArrayGetCount(login_items_array.get()); ++i) {
LSSharedFileListItemRef item =
reinterpret_cast<LSSharedFileListItemRef>(const_cast<void *>(
CFArrayGetValueAtIndex(login_items_array.get(), i)));
if (!item) {
LOG(ERROR) << "CFArrayGetValueAtIndex error:"
<< " Cannot get the login item.";
return NULL;
}
CFURLRef item_url_ref = NULL;
if (LSSharedFileListItemResolve(item, 0, &item_url_ref, NULL) == noErr) {
if (!item_url_ref) {
LOG(ERROR) << "LSSharedFileListItemResolve error:"
<< " Cannot get the login item url.";
return NULL;
}
if (CFEqual(item_url_ref, url.get())) {
prelauncher_item = item;
CFRetain(prelauncher_item);
}
}
}
return prelauncher_item;
}
} // anonymous namespace
string MacUtil::GetLabelForSuffix(const string &suffix) {
return string(kProjectPrefix) + suffix;
}
string MacUtil::GetApplicationSupportDirectory() {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
string dir;
NSArray *paths = NSSearchPathForDirectoriesInDomains(
NSApplicationSupportDirectory, NSUserDomainMask, YES);
if ([paths count] > 0) {
dir.assign([[paths objectAtIndex:0] fileSystemRepresentation]);
}
[pool drain];
return dir;
}
string MacUtil::GetLoggingDirectory() {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
string dir;
NSArray *paths = NSSearchPathForDirectoriesInDomains(
NSLibraryDirectory, NSUserDomainMask, YES);
if ([paths count] > 0) {
dir.assign(
[[[[paths objectAtIndex:0] stringByAppendingPathComponent:@"Logs"]
stringByAppendingPathComponent:@kProductPrefix]
fileSystemRepresentation]);
}
[pool drain];
return dir;
}
string MacUtil::GetOSVersionString() {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
string version;
version.assign([[[NSProcessInfo processInfo] operatingSystemVersionString]
cStringUsingEncoding : NSUTF8StringEncoding]);
[pool drain];
return version;
}
namespace {
class OSVersionCache {
public:
OSVersionCache() : succeeded_(false) {
// TODO(horo): Gestalt function is deprecated in OS X v10.8.
// Consider how to get the version after 10.8.
if (Gestalt(gestaltSystemVersionMajor, &major_version_) == noErr &&
Gestalt(gestaltSystemVersionMinor, &minor_version_) == noErr &&
Gestalt(gestaltSystemVersionBugFix, &fix_version_) == noErr) {
succeeded_ = true;
}
}
bool GetOSVersion(int32 *major, int32 *minor, int32 *fix) const {
if (!succeeded_) {
return false;
}
*major = major_version_;
*minor = minor_version_;
*fix = fix_version_;
return true;
}
private:
SInt32 major_version_;
SInt32 minor_version_;
SInt32 fix_version_;
bool succeeded_;
};
} // namespace
bool MacUtil::GetOSVersion(int32 *major, int32 *minor, int32 *fix) {
return Singleton<OSVersionCache>::get()->GetOSVersion(major, minor, fix);
}
bool MacUtil::OSVersionIsGreaterOrEqual(int32 major, int32 minor, int32 fix) {
int32 major_version = 0;
int32 minor_version = 0;
int32 fix_version = 0;
if (!GetOSVersion(&major_version, &minor_version, &fix_version)) {
return false;
}
if ((major_version > major) ||
((major_version == major) &&
((minor_version > minor) ||
((minor_version == minor) && (fix_version >= fix))))) {
return true;
}
return false;
}
string MacUtil::GetServerDirectory() {
return kServerDirectory;
}
string MacUtil::GetResourcesDirectory() {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
string result;
NSBundle *mainBundle = [NSBundle mainBundle];
if (mainBundle) {
NSString *resourcePath = [mainBundle resourcePath];
if (resourcePath) {
result.assign([resourcePath fileSystemRepresentation]);
}
}
[pool drain];
return result;
}
string MacUtil::GetSerialNumber() {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Please refer to TN1103 for the details
// http://developer.apple.com/library/mac/#technotes/tn/tn1103.html
string result;
io_service_t platformExpert = IOServiceGetMatchingService(
kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
if (platformExpert) {
CFTypeRef serialNumberAsCFString =
IORegistryEntryCreateCFProperty(
platformExpert, CFSTR(kIOPlatformSerialNumberKey),
kCFAllocatorDefault, 0);
if (serialNumberAsCFString) {
const NSString *serialNumberNSString = reinterpret_cast<const NSString *>(
serialNumberAsCFString);
result.assign([serialNumberNSString UTF8String]);
}
IOObjectRelease(platformExpert);
}
[pool drain];
// Return the empty string if failed.
return result;
}
bool MacUtil::StartLaunchdService(const string &service_name,
pid_t *pid) {
int dummy_pid = 0;
if (pid == NULL) {
pid = &dummy_pid;
}
const string label = GetLabelForSuffix(service_name);
launch_data_t start_renderer_command =
launch_data_alloc(LAUNCH_DATA_DICTIONARY);
launch_data_dict_insert(start_renderer_command,
launch_data_new_string(label.c_str()),
LAUNCH_KEY_STARTJOB);
launch_data_t result_data = launch_msg(start_renderer_command);
launch_data_free(start_renderer_command);
if (result_data == NULL) {
LOG(ERROR) << "Failed to launch the specified service";
return false;
}
launch_data_free(result_data);
// Getting PID by using launch_msg API.
launch_data_t get_renderer_info =
launch_data_alloc(LAUNCH_DATA_DICTIONARY);
launch_data_dict_insert(get_renderer_info,
launch_data_new_string(label.c_str()),
LAUNCH_KEY_GETJOB);
launch_data_t renderer_info = launch_msg(get_renderer_info);
launch_data_free(get_renderer_info);
if (renderer_info == NULL) {
LOG(ERROR) << "Unexpected error: launchd doesn't return the data "
<< "for the service.";
return false;
}
launch_data_t pid_data = launch_data_dict_lookup(
renderer_info, LAUNCH_JOBKEY_PID);
if (pid_data == NULL) {
LOG(ERROR) <<
"Unexpected error: launchd response doesn't have PID";
launch_data_free(renderer_info);
return false;
}
*pid = launch_data_get_integer(pid_data);
launch_data_free(renderer_info);
return true;
}
bool MacUtil::CheckPrelauncherLoginItemStatus() {
scoped_cftyperef<LSSharedFileListItemRef> prelauncher_item(
GetPrelauncherLoginItem());
return (prelauncher_item.get() != NULL);
}
void MacUtil::RemovePrelauncherLoginItem() {
scoped_cftyperef<LSSharedFileListItemRef> prelauncher_item(
GetPrelauncherLoginItem());
if (!prelauncher_item.get()) {
DLOG(INFO) << "prelauncher_item not found. Probably not registered yet.";
return;
}
scoped_cftyperef<LSSharedFileListRef> login_items(
LSSharedFileListCreate(
kCFAllocatorDefault, kLSSharedFileListSessionLoginItems, NULL));
if (!login_items.get()) {
LOG(ERROR) << "LSSharedFileListCreate error: Cannot get the login items.";
return;
}
LSSharedFileListItemRemove(login_items.get(), prelauncher_item.get());
}
void MacUtil::AddPrelauncherLoginItem() {
if (CheckPrelauncherLoginItemStatus()) {
return;
}
scoped_cftyperef<LSSharedFileListRef> login_items(
LSSharedFileListCreate(
kCFAllocatorDefault, kLSSharedFileListSessionLoginItems, NULL));
if (!login_items.get()) {
LOG(ERROR) << "LSSharedFileListCreate error: Cannot get the login items.";
return;
}
scoped_cftyperef<CFURLRef> url(
CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault, kPrelauncherPath,
strlen((const char *)kPrelauncherPath), true));
if (!url.get()) {
LOG(ERROR) << "CFURLCreateFromFileSystemRepresentation error:"
<< " Cannot create CFURL object.";
return;
}
scoped_cftyperef<LSSharedFileListItemRef> new_item(
LSSharedFileListInsertItemURL(
login_items.get(), kLSSharedFileListItemLast, NULL, NULL, url.get(),
NULL, NULL));
if (!new_item.get()) {
LOG(ERROR) << "LSSharedFileListInsertItemURL error:"
<< " Cannot insert the prelauncher to the login items.";
return;
}
}
bool MacUtil::GetFrontmostWindowNameAndOwner(string *name, string *owner) {
DCHECK(name);
DCHECK(owner);
scoped_cftyperef<CFArrayRef> window_list(
::CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
kCGNullWindowID));
const CFIndex window_count = CFArrayGetCount(window_list.get());
for (CFIndex i = 0; i < window_count; ++i) {
const NSDictionary *window_data = static_cast<const NSDictionary *>(
CFArrayGetValueAtIndex(window_list.get(), i));
if ([[window_data objectForKey:(id)kCGWindowSharingState] intValue] ==
kCGWindowSharingNone) {
// Skips not shared window.
continue;
}
NSString *window_name = [window_data objectForKey:(id)kCGWindowName];
NSString *owner_name =
[window_data objectForKey:(id)kCGWindowOwnerName];
NSNumber *window_layer = [window_data objectForKey:(id)kCGWindowLayer];
if ((window_name == nil) || (owner_name == nil) || (window_layer == nil)) {
continue;
}
// Ignores the windows which aren't normal window level.
if ([window_layer intValue] != kCGNormalWindowLevel) {
continue;
}
// Hack to ignore the window (name == "" and owner == "Google Chrome")
// Chrome browser seems to create a window which has no name in front of the
// actual frontmost Chrome window.
if ([window_name isEqualToString:@""] &&
[owner_name isEqualToString:@"Google Chrome"]) {
continue;
}
name->assign([window_name UTF8String]);
owner->assign([owner_name UTF8String]);
return true;
}
return false;
}
bool MacUtil::IsSuppressSuggestionWindow(const string &name,
const string &owner) {
// TODO(horo): Make a function to check the name, then share it with the
// Windows client.
// Currently we don't support "Firefox", because in Firefox "activateServer:"
// of IMKStateSetting Protocol is not called when the user changes the
// browsing tab.
return (("Google Chrome" == owner) ||
("Safari" == owner)) &&
(("Google" == name) ||
Util::EndsWith(
name,
" - Google \xE6\xA4\x9C\xE7\xB4\xA2") || // " - Google 検索"
Util::EndsWith(name, " - Google Search"));
}
} // namespace mozc