| // 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 "ActivatePane.h" |
| |
| #import <Carbon/Carbon.h> |
| #import <Foundation/Foundation.h> |
| |
| #ifdef GOOGLE_JAPANESE_INPUT_BUILD |
| static const unsigned char kInstalledLocation[] = |
| "/Library/Input Methods/GoogleJapaneseInput.app"; |
| static NSString *kLaunchdPlistFiles[] = { |
| @"/Library/LaunchAgents/com.google.inputmethod.Japanese.Converter.plist", |
| @"/Library/LaunchAgents/com.google.inputmethod.Japanese.Renderer.plist", |
| nil}; |
| static NSString *const kSourceID = @"com.google.inputmethod.Japanese"; |
| #else // GOOGLE_JAPANESE_INPUT_BUILD |
| static const unsigned char kInstalledLocation[] = |
| "/Library/Input Methods/Mozc.app"; |
| static NSString *kLaunchdPlistFiles[] = { |
| @"/Library/LaunchAgents/org.mozc.inputmethod.Japanese.Converter.plist", |
| @"/Library/LaunchAgents/org.mozc.inputmethod.Japanese.Renderer.plist", |
| nil}; |
| static NSString *const kSourceID = @"org.mozc.inputmethod.Japanese"; |
| #endif // GOOGLE_JAPANESE_INPUT_BUILD |
| |
| // Load the installed Google Japanese Input to the system. |
| static void RegisterGoogleJapaneseInput() { |
| CFURLRef installedLocationURL = CFURLCreateFromFileSystemRepresentation( |
| NULL, kInstalledLocation, strlen((const char *)kInstalledLocation), NO); |
| if (installedLocationURL) { |
| TISRegisterInputSource(installedLocationURL); |
| } |
| } |
| |
| // Put modes of Google Japanese Input at the list of pull-down menu |
| // for IMEs, and make it as the default IME. |
| static void ActivateGoogleJapaneseInput() { |
| CFArrayRef sourceList = TISCreateInputSourceList(NULL, true); |
| for (int i = 0; i < CFArrayGetCount(sourceList); ++i) { |
| TISInputSourceRef inputSource = (TISInputSourceRef)(CFArrayGetValueAtIndex( |
| sourceList, i)); |
| NSString *sourceID = (NSString *)(TISGetInputSourceProperty( |
| inputSource, kTISPropertyInputSourceID)); |
| if ([sourceID isEqualToString:kSourceID]) { |
| TISEnableInputSource(inputSource); |
| TISSelectInputSource(inputSource); |
| break; |
| } |
| } |
| } |
| |
| // Load the launchd preferences |
| static void LoadLaunchdPlistFiles() { |
| int i = 0; |
| for (; kLaunchdPlistFiles[i] != nil; ++i) { |
| NSArray *arguments = |
| [NSArray arrayWithObjects:@"load", @"-S", @"Aqua", |
| kLaunchdPlistFiles[i], nil]; |
| NSTask *task = [NSTask launchedTaskWithLaunchPath:@"/bin/launchctl" |
| arguments:arguments]; |
| [task waitUntilExit]; |
| } |
| } |
| |
| // Check if Google Japanese Input is already active on the user |
| // machine (e.g. the user already installed Google Japanese Input). |
| // Called at the initialization of this module. |
| static BOOL IsAlreadyActive() { |
| BOOL isActive = NO; |
| CFArrayRef sourceList = TISCreateInputSourceList(NULL, true); |
| for (int i = 0; i < CFArrayGetCount(sourceList); ++i) { |
| TISInputSourceRef inputSource = (TISInputSourceRef)(CFArrayGetValueAtIndex( |
| sourceList, i)); |
| NSString *sourceID = (NSString *)(TISGetInputSourceProperty( |
| inputSource, kTISPropertyInputSourceID)); |
| if ([sourceID isEqualToString:kSourceID]) { |
| CFBooleanRef isEnabled = (CFBooleanRef)(TISGetInputSourceProperty( |
| inputSource, kTISPropertyInputSourceIsEnabled)); |
| CFBooleanRef isSelected = (CFBooleanRef)(TISGetInputSourceProperty( |
| inputSource, kTISPropertyInputSourceIsEnabled)); |
| if (CFBooleanGetValue(isEnabled) || CFBooleanGetValue(isSelected)) { |
| isActive = YES; |
| break; |
| } |
| } |
| } |
| return isActive; |
| } |
| |
| // Check if the usagestats.db file already exists. Called at the |
| // initialization of this module. |
| static BOOL HasUsageStatsDB() { |
| // If channel is dev, we don't want to show the "turn-on usage |
| // stats" button, so the situation is same as that there are already |
| // usage stats config file. |
| #ifndef GOOGLE_JAPANESE_INPUT_BUILD |
| return NO; |
| #elif defined(CHANNEL_DEV) |
| return YES; |
| #endif // CHANNEL_DEV |
| NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
| NSArray *libraryPaths = NSSearchPathForDirectoriesInDomains( |
| NSLibraryDirectory, NSUserDomainMask, YES); |
| NSFileManager *fileManager = [NSFileManager defaultManager]; |
| NSString *libraryPath = nil; |
| NSString *usageStatsDBPath = nil; |
| BOOL exists = NO; |
| if (libraryPaths == nil || [libraryPaths count] == 0) { |
| [pool drain]; |
| return NO; |
| } |
| libraryPath = [libraryPaths objectAtIndex:0]; |
| usageStatsDBPath = |
| [[[[libraryPath stringByAppendingPathComponent:@"Application Support"] |
| stringByAppendingPathComponent:@"Google"] |
| stringByAppendingPathComponent:@"JapaneseInput"] |
| stringByAppendingPathComponent:@".usagestats.db"]; |
| exists = [fileManager fileExistsAtPath:usageStatsDBPath]; |
| [pool drain]; |
| return exists; |
| } |
| |
| // Put the usagestats file of sending report onto the user's config |
| // directory. This function should be called only if the user checks |
| // the |_putUsageStatsDB| button. |
| static BOOL StoreDefaultConfigWithSendingUsageStats() { |
| #ifndef GOOGLE_JAPANESE_INPUT_BUILD |
| return NO; |
| #endif // !GOOGLE_JAPANESE_INPUT_BUILD |
| NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
| NSArray *libraryPaths = NSSearchPathForDirectoriesInDomains( |
| NSLibraryDirectory, NSUserDomainMask, YES); |
| NSFileManager *fileManager = [NSFileManager defaultManager]; |
| NSString *libraryPath = nil; |
| NSString *googlePath = nil; |
| NSString *japaneseInputPath = nil; |
| NSString *usageStatsDBPath = nil; |
| if (libraryPaths == nil || [libraryPaths count] == 0) { |
| [pool drain]; |
| return NO; |
| } |
| |
| libraryPath = [libraryPaths objectAtIndex:0]; |
| googlePath = |
| [[libraryPath stringByAppendingPathComponent:@"Application Support"] |
| stringByAppendingPathComponent:@"Google"]; |
| if (![fileManager fileExistsAtPath:googlePath]) { |
| NSDictionary *defaultAttributes = |
| [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:0755] |
| forKey:NSFilePosixPermissions]; |
| if (![fileManager createDirectoryAtPath:googlePath |
| withIntermediateDirectories:YES |
| attributes:defaultAttributes |
| error:NULL]) { |
| [pool drain]; |
| return NO; |
| } |
| } |
| |
| japaneseInputPath = |
| [googlePath stringByAppendingPathComponent:@"JapaneseInput"]; |
| if (![fileManager fileExistsAtPath:japaneseInputPath]) { |
| NSDictionary *japaneseInputAttributes = |
| [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:0700] |
| forKey:NSFilePosixPermissions]; |
| if (![fileManager createDirectoryAtPath:japaneseInputPath |
| withIntermediateDirectories:YES |
| attributes:japaneseInputAttributes |
| error:NULL]) { |
| [pool drain]; |
| return NO; |
| } |
| } |
| |
| usageStatsDBPath = |
| [japaneseInputPath stringByAppendingPathComponent:@".usagestats.db"]; |
| if (![fileManager fileExistsAtPath:usageStatsDBPath]) { |
| // The value of usage stats is a 32-bit int and 1 means "sending |
| // the usage stats to Google". |
| const int sending = 1; |
| NSDictionary *usageStatsDBAttributes = |
| [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:0400] |
| forKey:NSFilePosixPermissions]; |
| if ([fileManager createFileAtPath:usageStatsDBPath |
| contents:[NSData dataWithBytes:&sending |
| length:4] |
| attributes:usageStatsDBAttributes]) { |
| [pool drain]; |
| return YES; |
| } |
| } |
| |
| [pool drain]; |
| return NO; |
| } |
| |
| @interface ActivatePane () |
| // Returns localized string for the key in the bundle. Returns nil if |
| // not found. This is a wrapper of [NSBundle localizedStringForKey:] |
| // which returns the key if not found. |
| - (NSString *)localizedStringForKey:(NSString *)key; |
| @end |
| |
| @implementation ActivatePane |
| - (id)initWithSection:(id)parent { |
| self = [super initWithSection:parent]; |
| _alreadyActivated = NO; |
| _isUpgrade = NO; |
| _hasUsageStatsDB = NO; |
| return self; |
| } |
| |
| // This method is called once when this module is loaded. |
| - (void)awakeFromNib { |
| _isUpgrade = IsAlreadyActive(); |
| _hasUsageStatsDB = _isUpgrade || HasUsageStatsDB(); |
| if (_isUpgrade) { |
| _alreadyActivated = YES; |
| } |
| } |
| |
| - (NSString *)title { |
| NSString *titleString = [self localizedStringForKey:@"Title"]; |
| if (titleString) { |
| return titleString; |
| } |
| return @"Automatic Activation"; |
| } |
| |
| // This method is called when the pane is visible. |
| - (void)willEnterPane:(InstallerSectionDirection)dir { |
| // Main messages |
| NSString *mainMessage = [self localizedStringForKey:@"mainMessage"]; |
| if (mainMessage) { |
| [_mainMessage setStringValue:mainMessage]; |
| } |
| |
| // Upgrade Message. Blank if it's not upgrade. |
| if (_isUpgrade) { |
| NSString *upgradeMessage = [self localizedStringForKey:@"upgradeMessage"]; |
| if (upgradeMessage) { |
| [_upgradeMessage setStringValue:upgradeMessage]; |
| } |
| } else { |
| [_upgradeMessage setStringValue:@""]; |
| } |
| |
| // Button label for "activate" |
| NSString *activateLabel = [self localizedStringForKey:@"activateLabel"]; |
| if (activateLabel) { |
| [_activateCell setTitle:activateLabel]; |
| } |
| |
| // Button label for "do not activate" |
| NSString *doNotActivateLabel = |
| [self localizedStringForKey:@"doNotActivateLabel"]; |
| if (doNotActivateLabel) { |
| [_doNotActivateCell setTitle:doNotActivateLabel]; |
| } |
| |
| // usage stats message |
| #ifndef GOOGLE_JAPANESE_INPUT_BUILD |
| [_putUsageStatsDB removeFromSuperview]; |
| [_putUsageStatsDBMessage removeFromSuperview]; |
| #else // GOOGLE_JAPANESE_INPUT_BUILD |
| if (!_hasUsageStatsDB) { |
| NSString *usageStatsMessage = |
| [self localizedStringForKey:@"usageStatsMessage"]; |
| if (usageStatsMessage) { |
| [_putUsageStatsDBMessage setStringValue:usageStatsMessage]; |
| } |
| } else { |
| [_putUsageStatsDB removeFromSuperview]; |
| [_putUsageStatsDBMessage removeFromSuperview]; |
| } |
| #endif // GOOGLE_JAPANESE_INPUT_BUILD |
| |
| if (_alreadyActivated) { |
| [_mainMessage setEnabled:NO]; |
| [_activateCell setEnabled:NO]; |
| [_doNotActivateCell setEnabled:NO]; |
| } |
| } |
| |
| // This method is called when the user leave the pane. |
| - (void)willExitPane:(InstallerSectionDirection)dir { |
| if (dir == InstallerDirectionForward) { |
| LoadLaunchdPlistFiles(); |
| } |
| |
| if (!_alreadyActivated && |
| dir == InstallerDirectionForward) { |
| // Make the package visible from i18n system preference |
| RegisterGoogleJapaneseInput(); |
| if ([_activateCell state] == NSOnState && |
| [_doNotActivateCell state] == NSOffState) { |
| // means clicks "next page" when "activate" menu is on |
| ActivateGoogleJapaneseInput(); |
| _alreadyActivated = YES; |
| } |
| #ifdef GOOGLE_JAPANESE_INPUT_BUILD |
| if (!_hasUsageStatsDB && [_putUsageStatsDB state] == NSOnState) { |
| StoreDefaultConfigWithSendingUsageStats(); |
| } |
| #endif // GOOGLE_JAPANESE_INPUT_BUILD |
| } |
| } |
| |
| |
| - (NSString *)localizedStringForKey:(NSString *)key { |
| NSBundle *bundle = [NSBundle bundleForClass:[self class]]; |
| if (!bundle) { |
| return nil; |
| } |
| |
| NSString *value = [bundle localizedStringForKey:key value:nil table:nil]; |
| // value is same as "key" if failed. |
| if ([value isEqualToString:key]) { |
| return nil; |
| } |
| return value; |
| } |
| @end |