1// Copyright 2010-2018, Google Inc. 2// All rights reserved. 3// 4// Redistribution and use in source and binary forms, with or without 5// modification, are permitted provided that the following conditions are 6// met: 7// 8// * Redistributions of source code must retain the above copyright 9// notice, this list of conditions and the following disclaimer. 10// * Redistributions in binary form must reproduce the above 11// copyright notice, this list of conditions and the following disclaimer 12// in the documentation and/or other materials provided with the 13// distribution. 14// * Neither the name of Google Inc. nor the names of its 15// contributors may be used to endorse or promote products derived from 16// this software without specific prior written permission. 17// 18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30#import "base/mac_util.h" 31 32#import <Foundation/Foundation.h> 33 34#include <launch.h> 35#include <CoreFoundation/CoreFoundation.h> 36#include <IOKit/IOKitLib.h> 37 38#include "base/const.h" 39#include "base/logging.h" 40#include "base/scoped_cftyperef.h" 41#include "base/singleton.h" 42#include "base/util.h" 43 44namespace mozc { 45namespace { 46const char kServerDirectory[] = 47 "/Library/Input Methods/" kProductPrefix ".app/Contents/Resources"; 48const unsigned char kPrelauncherPath[] = 49 "/Library/Input Methods/" kProductPrefix ".app/Contents/Resources/" 50 kProductPrefix "Prelauncher.app"; 51 52#ifdef GOOGLE_JAPANESE_INPUT_BUILD 53const char kProjectPrefix[] = 54 "com.google.inputmethod.Japanese."; 55#elif defined(MOZC_BUILD) 56const char kProjectPrefix[] = 57 "org.mozc.inputmethod.Japanese."; 58#else 59#error Unknown branding 60#endif 61 62// Returns the reference of prelauncher login item. 63// If the prelauncher login item does not exist this function returns nullptr. 64// Otherwise you must release the reference. 65LSSharedFileListItemRef GetPrelauncherLoginItem() { 66 LSSharedFileListItemRef prelauncher_item = nullptr; 67 scoped_cftyperef<CFURLRef> url( 68 CFURLCreateFromFileSystemRepresentation( 69 kCFAllocatorDefault, kPrelauncherPath, 70 strlen((const char *)kPrelauncherPath), true)); 71 if (!url.get()) { 72 LOG(ERROR) << "CFURLCreateFromFileSystemRepresentation error:" 73 << " Cannot create CFURL object."; 74 return nullptr; 75 } 76 77 scoped_cftyperef<LSSharedFileListRef> login_items( 78 LSSharedFileListCreate( 79 kCFAllocatorDefault, kLSSharedFileListSessionLoginItems, nullptr)); 80 if (!login_items.get()) { 81 LOG(ERROR) << "LSSharedFileListCreate error: Cannot get the login items."; 82 return nullptr; 83 } 84 85 scoped_cftyperef<CFArrayRef> login_items_array( 86 LSSharedFileListCopySnapshot(login_items.get(), nullptr)); 87 if (!login_items_array.get()) { 88 LOG(ERROR) << "LSSharedFileListCopySnapshot error:" 89 << " Cannot get the login items."; 90 return nullptr; 91 } 92 93 for(CFIndex i = 0; i < CFArrayGetCount(login_items_array.get()); ++i) { 94 LSSharedFileListItemRef item = 95 reinterpret_cast<LSSharedFileListItemRef>(const_cast<void *>( 96 CFArrayGetValueAtIndex(login_items_array.get(), i))); 97 if (!item) { 98 LOG(ERROR) << "CFArrayGetValueAtIndex error:" 99 << " Cannot get the login item."; 100 return nullptr; 101 } 102 103 CFURLRef item_url_ref = nullptr; 104 if (LSSharedFileListItemResolve(item, 0, &item_url_ref, nullptr) == noErr) { 105 if (!item_url_ref) { 106 LOG(ERROR) << "LSSharedFileListItemResolve error:" 107 << " Cannot get the login item url."; 108 return nullptr; 109 } 110 if (CFEqual(item_url_ref, url.get())) { 111 prelauncher_item = item; 112 CFRetain(prelauncher_item); 113 } 114 } 115 } 116 117 return prelauncher_item; 118} 119 120string GetSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory) { 121 string dir; 122 @autoreleasepool { 123 NSArray *paths = NSSearchPathForDirectoriesInDomains( 124 directory, NSUserDomainMask, YES); 125 if ([paths count] > 0) { 126 dir.assign([[paths objectAtIndex:0] fileSystemRepresentation]); 127 } 128 } 129 return dir; 130} 131 132} // namespace 133 134string MacUtil::GetLabelForSuffix(const string &suffix) { 135 return string(kProjectPrefix) + suffix; 136} 137 138string MacUtil::GetApplicationSupportDirectory() { 139 return GetSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory); 140} 141 142string MacUtil::GetCachesDirectory() { 143 return GetSearchPathForDirectoriesInDomains(NSCachesDirectory); 144} 145 146string MacUtil::GetLoggingDirectory() { 147 string dir; 148 @autoreleasepool { 149 NSArray *paths = NSSearchPathForDirectoriesInDomains( 150 NSLibraryDirectory, NSUserDomainMask, YES); 151 if ([paths count] > 0) { 152 dir.assign( 153 [[[[paths objectAtIndex:0] stringByAppendingPathComponent:@"Logs"] 154 stringByAppendingPathComponent:@kProductPrefix] 155 fileSystemRepresentation]); 156 } 157 } 158 return dir; 159} 160 161string MacUtil::GetOSVersionString() { 162 string version; 163 @autoreleasepool { 164 version.assign([[[NSProcessInfo processInfo] operatingSystemVersionString] 165 cStringUsingEncoding:NSUTF8StringEncoding]); 166 } 167 return version; 168} 169 170string MacUtil::GetServerDirectory() { 171 return kServerDirectory; 172} 173 174string MacUtil::GetResourcesDirectory() { 175 string result; 176 @autoreleasepool { 177 NSBundle *mainBundle = [NSBundle mainBundle]; 178 if (mainBundle) { 179 NSString *resourcePath = [mainBundle resourcePath]; 180 if (resourcePath) { 181 result.assign([resourcePath fileSystemRepresentation]); 182 } 183 } 184 } 185 return result; 186} 187 188string MacUtil::GetSerialNumber() { 189 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 190 // Please refer to TN1103 for the details 191 // http://developer.apple.com/library/mac/#technotes/tn/tn1103.html 192 string result; 193 io_service_t platformExpert = IOServiceGetMatchingService( 194 kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); 195 196 if (platformExpert) { 197 CFTypeRef serialNumberAsCFString = 198 IORegistryEntryCreateCFProperty( 199 platformExpert, CFSTR(kIOPlatformSerialNumberKey), 200 kCFAllocatorDefault, 0); 201 if (serialNumberAsCFString) { 202 const NSString *serialNumberNSString = reinterpret_cast<const NSString *>( 203 serialNumberAsCFString); 204 result.assign([serialNumberNSString UTF8String]); 205 } 206 207 IOObjectRelease(platformExpert); 208 } 209 210 [pool drain]; 211 // Return the empty string if failed. 212 return result; 213} 214 215bool MacUtil::StartLaunchdService(const string &service_name, 216 pid_t *pid) { 217 int dummy_pid = 0; 218 if (pid == nullptr) { 219 pid = &dummy_pid; 220 } 221 const string label = GetLabelForSuffix(service_name); 222 223 launch_data_t start_renderer_command = 224 launch_data_alloc(LAUNCH_DATA_DICTIONARY); 225 launch_data_dict_insert(start_renderer_command, 226 launch_data_new_string(label.c_str()), 227 LAUNCH_KEY_STARTJOB); 228 launch_data_t result_data = launch_msg(start_renderer_command); 229 launch_data_free(start_renderer_command); 230 if (result_data == nullptr) { 231 LOG(ERROR) << "Failed to launch the specified service"; 232 return false; 233 } 234 launch_data_free(result_data); 235 236 // Getting PID by using launch_msg API. 237 launch_data_t get_renderer_info = 238 launch_data_alloc(LAUNCH_DATA_DICTIONARY); 239 launch_data_dict_insert(get_renderer_info, 240 launch_data_new_string(label.c_str()), 241 LAUNCH_KEY_GETJOB); 242 launch_data_t renderer_info = launch_msg(get_renderer_info); 243 launch_data_free(get_renderer_info); 244 if (renderer_info == nullptr) { 245 LOG(ERROR) << "Unexpected error: launchd doesn't return the data " 246 << "for the service."; 247 return false; 248 } 249 250 launch_data_t pid_data = launch_data_dict_lookup( 251 renderer_info, LAUNCH_JOBKEY_PID); 252 if (pid_data == nullptr) { 253 LOG(ERROR) << 254 "Unexpected error: launchd response doesn't have PID"; 255 launch_data_free(renderer_info); 256 return false; 257 } 258 *pid = launch_data_get_integer(pid_data); 259 launch_data_free(renderer_info); 260 return true; 261} 262 263bool MacUtil::CheckPrelauncherLoginItemStatus() { 264 scoped_cftyperef<LSSharedFileListItemRef> prelauncher_item( 265 GetPrelauncherLoginItem()); 266 return (prelauncher_item.get() != nullptr); 267} 268 269void MacUtil::RemovePrelauncherLoginItem() { 270 scoped_cftyperef<LSSharedFileListItemRef> prelauncher_item( 271 GetPrelauncherLoginItem()); 272 273 if (!prelauncher_item.get()) { 274 DLOG(INFO) << "prelauncher_item not found. Probably not registered yet."; 275 return; 276 } 277 scoped_cftyperef<LSSharedFileListRef> login_items( 278 LSSharedFileListCreate( 279 kCFAllocatorDefault, kLSSharedFileListSessionLoginItems, nullptr)); 280 if (!login_items.get()) { 281 LOG(ERROR) << "LSSharedFileListCreate error: Cannot get the login items."; 282 return; 283 } 284 LSSharedFileListItemRemove(login_items.get(), prelauncher_item.get()); 285} 286 287void MacUtil::AddPrelauncherLoginItem() { 288 if (CheckPrelauncherLoginItemStatus()) { 289 return; 290 } 291 scoped_cftyperef<LSSharedFileListRef> login_items( 292 LSSharedFileListCreate( 293 kCFAllocatorDefault, kLSSharedFileListSessionLoginItems, nullptr)); 294 if (!login_items.get()) { 295 LOG(ERROR) << "LSSharedFileListCreate error: Cannot get the login items."; 296 return; 297 } 298 scoped_cftyperef<CFURLRef> url( 299 CFURLCreateFromFileSystemRepresentation( 300 kCFAllocatorDefault, kPrelauncherPath, 301 strlen((const char *)kPrelauncherPath), true)); 302 303 if (!url.get()) { 304 LOG(ERROR) << "CFURLCreateFromFileSystemRepresentation error:" 305 << " Cannot create CFURL object."; 306 return; 307 } 308 scoped_cftyperef<LSSharedFileListItemRef> new_item( 309 LSSharedFileListInsertItemURL( 310 login_items.get(), kLSSharedFileListItemLast, nullptr, nullptr, url.get(), 311 nullptr, nullptr)); 312 if (!new_item.get()) { 313 LOG(ERROR) << "LSSharedFileListInsertItemURL error:" 314 << " Cannot insert the prelauncher to the login items."; 315 return; 316 } 317} 318 319bool MacUtil::GetFrontmostWindowNameAndOwner(string *name, string *owner) { 320 DCHECK(name); 321 DCHECK(owner); 322 scoped_cftyperef<CFArrayRef> window_list( 323 ::CGWindowListCopyWindowInfo( 324 kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, 325 kCGNullWindowID)); 326 const CFIndex window_count = CFArrayGetCount(window_list.get()); 327 for (CFIndex i = 0; i < window_count; ++i) { 328 const NSDictionary *window_data = static_cast<const NSDictionary *>( 329 CFArrayGetValueAtIndex(window_list.get(), i)); 330 if ([[window_data objectForKey:(id)kCGWindowSharingState] intValue] == 331 kCGWindowSharingNone) { 332 // Skips not shared window. 333 continue; 334 } 335 NSString *window_name = [window_data objectForKey:(id)kCGWindowName]; 336 NSString *owner_name = 337 [window_data objectForKey:(id)kCGWindowOwnerName]; 338 NSNumber *window_layer = [window_data objectForKey:(id)kCGWindowLayer]; 339 340 if ((window_name == nil) || (owner_name == nil) || (window_layer == nil)) { 341 continue; 342 } 343 // Ignores the windows which aren't normal window level. 344 if ([window_layer intValue] != kCGNormalWindowLevel) { 345 continue; 346 } 347 348 // Hack to ignore the window (name == "" and owner == "Google Chrome") 349 // Chrome browser seems to create a window which has no name in front of the 350 // actual frontmost Chrome window. 351 if ([window_name isEqualToString:@""] && 352 [owner_name isEqualToString:@"Google Chrome"]) { 353 continue; 354 } 355 name->assign([window_name UTF8String]); 356 owner->assign([owner_name UTF8String]); 357 return true; 358 } 359 return false; 360} 361 362bool MacUtil::IsSuppressSuggestionWindow(const string &name, 363 const string &owner) { 364 // TODO(horo): Make a function to check the name, then share it with the 365 // Windows client. 366 // Currently we don't support "Firefox", because in Firefox "activateServer:" 367 // of IMKStateSetting Protocol is not called when the user changes the 368 // browsing tab. 369 return (("Google Chrome" == owner) || 370 ("Safari" == owner)) && 371 (("Google" == name) || 372 Util::EndsWith( 373 name, 374 " - Google \xE6\xA4\x9C\xE7\xB4\xA2") || // " - Google 検索" 375 Util::EndsWith(name, " - Google Search")); 376} 377 378} // namespace mozc 379