Merge pull request #12355 from keymanapp/change/mac/12342-remove-always-show-osk

change(mac): remove 'Always show OSK' option
This commit is contained in:
Shawn Schantz 2024-09-17 08:50:18 +07:00 committed by GitHub
commit 492baead2c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 419 additions and 199 deletions

View file

@ -14,6 +14,7 @@
293EA3E627140D8100545EED /* KMAboutWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 293EA3E827140D8100545EED /* KMAboutWindowController.xib */; };
293EA3EB27140DEC00545EED /* preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 293EA3ED27140DEC00545EED /* preferences.xib */; };
293EA3F427181FDA00545EED /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 293EA3F627181FDA00545EED /* Localizable.strings */; };
296105232C8E91C7007BF6B7 /* KMInputMethodLifecycle.m in Sources */ = {isa = PBXBuildFile; fileRef = 296105222C8E91C7007BF6B7 /* KMInputMethodLifecycle.m */; };
296FE2FC275DD21600F46898 /* KMPackageReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 296FE2FB275DD21600F46898 /* KMPackageReader.m */; };
297A501728DF4D360074EB1B /* PrivacyWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 297A501228DF4D360074EB1B /* PrivacyWindowController.m */; };
297A501828DF4D360074EB1B /* PrivacyWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 297A501328DF4D360074EB1B /* PrivacyWindowController.xib */; };
@ -158,6 +159,8 @@
293EA3EF27140DFA00545EED /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/preferences.strings; sourceTree = "<group>"; };
293EA3F02714158600545EED /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainMenu.strings; sourceTree = "<group>"; };
293EA3F527181FDA00545EED /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
296105212C8E91C7007BF6B7 /* KMInputMethodLifecycle.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMInputMethodLifecycle.h; sourceTree = "<group>"; };
296105222C8E91C7007BF6B7 /* KMInputMethodLifecycle.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMInputMethodLifecycle.m; sourceTree = "<group>"; };
296FE2FA275DD21600F46898 /* KMPackageReader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMPackageReader.h; sourceTree = "<group>"; };
296FE2FB275DD21600F46898 /* KMPackageReader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMPackageReader.m; sourceTree = "<group>"; };
29781101297FB262007C886D /* kn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kn; path = kn.lproj/KMAboutWindowController.strings; sourceTree = "<group>"; };
@ -579,6 +582,8 @@
98D6DA7D1A799FF400B09822 /* KMInputController.m */,
98A778C21A8C53BF00CF809D /* KMInputMethodAppDelegate.h */,
98A778C31A8C53BF00CF809D /* KMInputMethodAppDelegate.m */,
296105212C8E91C7007BF6B7 /* KMInputMethodLifecycle.h */,
296105222C8E91C7007BF6B7 /* KMInputMethodLifecycle.m */,
E21799031FC5B74D00F2D66A /* KMInputMethodEventHandler.h */,
E21799041FC5B7BC00F2D66A /* KMInputMethodEventHandler.m */,
298D09F62A1F4533006B9DFE /* TextApiCompliance.h */,
@ -993,6 +998,7 @@
29B4A0D52BF7675A00682049 /* KMLogs.m in Sources */,
98BF924F1BF02DC20002126A /* KMBarView.m in Sources */,
E240F599202DED740000067D /* KMPackage.m in Sources */,
296105232C8E91C7007BF6B7 /* KMInputMethodLifecycle.m in Sources */,
D861B03F2C5747F70003675E /* KMSettingsRepository.m in Sources */,
984B8F441AF1C3D900E096A8 /* OSKWindowController.m in Sources */,
9836B3711AE5F11D00780482 /* mztools.c in Sources */,

View file

@ -1,15 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<plugIn identifier="com.apple.WebKitIBPlugin" version="18122"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22689"/>
<plugIn identifier="com.apple.WebKitIBPlugin" version="22689"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="KMConfigurationWindowController">
<connections>
<outlet property="alwaysShowOSKCheckBox" destination="k5c-KF-717" id="EYm-1F-c71"/>
<outlet property="supportBack" destination="LX7-yF-rV2" id="ciN-Ia-7ei"/>
<outlet property="supportForward" destination="OSG-wW-VHs" id="8dG-cd-ByI"/>
<outlet property="supportHome" destination="RUX-eW-d6h" id="i5g-Lo-6zG"/>
@ -79,7 +78,7 @@
</textFieldCell>
<prototypeCellViews>
<tableCellView id="1fH-kz-di5" customClass="KMConfigColumn1CellView">
<rect key="frame" x="11" y="1" width="62" height="25"/>
<rect key="frame" x="1" y="1" width="62" height="25"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="mcD-zc-0eI">
@ -115,7 +114,7 @@
</textFieldCell>
<prototypeCellViews>
<tableCellView id="BtP-6C-zYt">
<rect key="frame" x="76" y="1" width="550" height="25"/>
<rect key="frame" x="66" y="1" width="550" height="25"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="YfS-ug-Llz">
@ -146,7 +145,7 @@
</textFieldCell>
<prototypeCellViews>
<tableCellView id="CGV-al-7wj" customClass="KMConfigColumn3CellView">
<rect key="frame" x="629" y="1" width="88" height="25"/>
<rect key="frame" x="619" y="1" width="88" height="25"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="mJA-up-QcJ">
@ -211,18 +210,7 @@
<rect key="frame" x="10" y="33" width="754" height="538"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button translatesAutoresizingMaskIntoConstraints="NO" id="k5c-KF-717">
<rect key="frame" x="15" y="509" width="234" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Always show on-screen keyboard" bezelStyle="regularSquare" imagePosition="left" inset="2" id="4UX-80-0Vo">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="alwaysShowOSKCheckBoxAction:" target="-2" id="TbQ-5n-ODK"/>
</connections>
</button>
<button toolTip="Turn this on if you are having problems with a specific keyboard or with Keyman in general." verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uWx-3J-U0D">
<button toolTip="Turn this on if you are having problems with a specific keyboard or with Keyman in general." verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uWx-3J-U0D">
<rect key="frame" x="15" y="483" width="200" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Use verbose Console logging" bezelStyle="regularSquare" imagePosition="left" inset="2" id="7J6-zy-R20">
@ -233,7 +221,7 @@
<action selector="useVerboseLoggingCheckBoxAction:" target="-2" id="DGX-fy-5qP"/>
</connections>
</button>
<textField hidden="YES" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PiJ-pY-UY7">
<textField hidden="YES" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PiJ-pY-UY7">
<rect key="frame" x="34" y="414" width="705" height="63"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" id="MrI-GM-7d6">
@ -254,7 +242,7 @@
<webView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gTQ-rF-m9S">
<rect key="frame" x="17" y="43" width="720" height="492"/>
<autoresizingMask key="autoresizingMask"/>
<webPreferences key="preferences" defaultFontSize="12" defaultFixedFontSize="12">
<webPreferences key="preferences" defaultFontSize="12" defaultFixedFontSize="12" javaEnabled="NO">
<nil key="identifier"/>
</webPreferences>
</webView>
@ -305,9 +293,9 @@
</window>
</objects>
<resources>
<image name="NSActionTemplate" width="15" height="15"/>
<image name="NSActionTemplate" width="19" height="19"/>
<image name="NSInfo" width="32" height="32"/>
<image name="NSRemoveTemplate" width="14" height="4"/>
<image name="NSRemoveTemplate" width="18" height="4"/>
<image name="buttonCell:clx-vL-wIY:image" width="1" height="1">
<mutableData key="keyedArchiveRepresentation">
YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05T

View file

@ -14,7 +14,6 @@
@interface KMConfigurationWindowController ()
@property (nonatomic, weak) IBOutlet NSTableView *tableView;
@property (nonatomic, weak) IBOutlet WebView *webView;
@property (nonatomic, weak) IBOutlet NSButton *alwaysShowOSKCheckBox;
@property (nonatomic, weak) IBOutlet NSButton *useVerboseLoggingCheckBox;
@property (nonatomic, weak) IBOutlet NSTextField *verboseLoggingInfo;
@property (nonatomic, weak) IBOutlet NSButton *supportBack;
@ -70,7 +69,6 @@
NSURL *homeUrl = [[NSBundle mainBundle] URLForResource:@"index" withExtension:@"html" subdirectory:@"Help"];
[self.webView.mainFrame loadRequest:[NSURLRequest requestWithURL:homeUrl]];
[self.alwaysShowOSKCheckBox setState:(self.AppDelegate.alwaysShowOSK ? NSOnState : NSOffState)];
[self.useVerboseLoggingCheckBox setState:(self.AppDelegate.useVerboseLogging ? NSOnState : NSOffState)];
}
@ -439,11 +437,6 @@
[self.AppDelegate.downloadKBWindow.window makeKeyAndOrderFront:nil];
}
- (IBAction)alwaysShowOSKCheckBoxAction:(id)sender {
NSButton *checkBox = (NSButton *)sender;
[self.AppDelegate setAlwaysShowOSK:(checkBox.state == NSOnState)];
}
- (IBAction)useVerboseLoggingCheckBoxAction:(id)sender {
NSButton *checkBox = (NSButton *)sender;
BOOL verboseLoggingOn = checkBox.state == NSOnState;

View file

@ -1,39 +1,46 @@
//
// KMInputController.m
// Keyman4MacIM
//
// Created by Serkan Kurt on 29/01/2015.
// Copyright (c) 2017 SIL International. All rights reserved.
//
/*
* Keyman is copyright (C) SIL International. MIT License.
*
* Created by Serkan Kurt on 2015-01-29.
*
*/
#import "KMInputController.h"
#import "KMInputMethodEventHandler.h"
#import "KMOSVersion.h"
#include <Carbon/Carbon.h> /* For kVK_ constants. */
#import "KMSettingsRepository.h"
#import "KMLogs.h"
#import "InputMethodKit/InputMethodKit.h"
#import "KMInputMethodLifecycle.h"
@implementation KMInputController
KMInputMethodEventHandler* _eventHandler;
NSMutableArray *servers;
- (KMInputMethodAppDelegate *)AppDelegate {
- (KMInputMethodAppDelegate *)appDelegate {
return (KMInputMethodAppDelegate *)[NSApp delegate];
}
- (id)initWithServer:(IMKServer *)server delegate:(id)delegate client:(id)inputClient
{
os_log_debug([KMLogs lifecycleLog], "Initializing Keyman Input Method for server with bundleID: %{public}@", server.bundle.bundleIdentifier);
os_log_debug([KMLogs lifecycleLog], "initWithServer, active app: '%{public}@'", [KMInputMethodLifecycle getClientApplicationId]);
self = [super initWithServer:server delegate:delegate client:inputClient];
if (self) {
servers = [[NSMutableArray alloc] initWithCapacity:2];
self.AppDelegate.inputController = self;
if (self.AppDelegate.kvk != nil && self.AppDelegate.alwaysShowOSK) {
[self.AppDelegate showOSK];
}
self.appDelegate.inputController = self;
}
/**
* Register to receive the Deactivated and ChangedClient notification generated from KMInputMethodLifecycle so
* that the eventHandler can be changed. There is no need to receive the Activated notification because
* the InputController does it all it needs to when it receives the ChangedClient.
*/
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(inputMethodDeactivated:) name:kInputMethodDeactivatedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(inputMethodChangedClient:) name:kInputMethodClientChangeNotification object:nil];
return self;
}
@ -59,102 +66,44 @@ NSMutableArray *servers;
}
}
- (void)activateServer:(id)sender {
@synchronized(servers) {
[sender overrideKeyboardWithKeyboardNamed:@"com.apple.keylayout.US"];
[self.AppDelegate wakeUpWith:sender];
[servers addObject:sender];
if (_eventHandler != nil) {
[_eventHandler deactivate];
}
NSRunningApplication *currApp = [[NSWorkspace sharedWorkspace] frontmostApplication];
NSString *clientAppId = [currApp bundleIdentifier];
os_log_debug([KMLogs lifecycleLog], "activateServer, new active app: '%{public}@'", clientAppId);
_eventHandler = [[KMInputMethodEventHandler alloc] initWithClient:clientAppId client:sender];
/**
* The Keyman input method is deactivating because the user chose a different input method: notification from KMInputMethodLifecycle
*/
- (void)inputMethodDeactivated:(NSNotification *)notification {
os_log_debug([KMLogs lifecycleLog], "***KMInputController inputMethodDeactivated, deactivating eventHandler");
if (_eventHandler != nil) {
[_eventHandler deactivate];
}
}
/**
* The user has switched to a different text input client: notification from KMInputMethodLifecycle
*/
- (void)inputMethodChangedClient:(NSNotification *)notification {
os_log_debug([KMLogs lifecycleLog], "***KMInputController inputMethodChangedClient, deactivating old eventHandler and activating new one");
if (_eventHandler != nil) {
[_eventHandler deactivate];
}
_eventHandler = [[KMInputMethodEventHandler alloc] initWithClient:[KMInputMethodLifecycle getClientApplicationId] client:self.client];
}
- (void)activateServer:(id)sender {
[sender overrideKeyboardWithKeyboardNamed:@"com.apple.keylayout.US"];
[KMInputMethodLifecycle.shared activateClient:sender];
}
- (void)deactivateServer:(id)sender {
if ([self.AppDelegate debugMode]) {
os_log_debug([KMLogs lifecycleLog], "deactivateServer, sender %{public}@", sender);
}
@synchronized(servers) {
for (int i = 0; i < servers.count; i++) {
if (servers[i] == sender) {
[servers removeObjectAtIndex:i];
break;
}
}
if (servers.count == 0) {
os_log_debug([KMLogs lifecycleLog], "No known active server for Keyman IM. Starting countdown to sleep...");
[self performSelector:@selector(timerAction:) withObject:sender afterDelay:0.7];
}
}
[KMInputMethodLifecycle.shared deactivateClient:sender];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)timerAction:(id)lastServer {
@synchronized(servers) {
if (servers.count == 0) {
if (_eventHandler != nil) {
[_eventHandler deactivate];
_eventHandler = nil;
}
[self.AppDelegate sleepFollowingDeactivationOfServer:lastServer];
}
}
}
/*
- (NSDictionary *)modes:(id)sender {
if ([self.AppDelegate debugMode])
os_log_debug([KMLogs lifecycleLog], "*** Modes ***");
if (_kmModes == nil) {
NSDictionary *amhMode = [[NSDictionary alloc] initWithObjectsAndKeys:@"keyman.png", kTSInputModeAlternateMenuIconFileKey,
[NSNumber numberWithBool:YES], kTSInputModeDefaultStateKey,
[NSNumber numberWithBool:YES], kTSInputModeIsVisibleKey,
@"A", kTSInputModeKeyEquivalentKey,
[NSNumber numberWithInteger:4608], kTSInputModeKeyEquivalentModifiersKey,
[NSNumber numberWithBool:YES], kTSInputModeDefaultStateKey,
@"keyman.png", kTSInputModeMenuIconFileKey,
@"keyman.png", kTSInputModePaletteIconFileKey,
[NSNumber numberWithBool:YES], kTSInputModePrimaryInScriptKey,
@"smUnicodeScript", kTSInputModeScriptKey,
@"amh", @"TISIntendedLanguage", nil];
NSDictionary *hinMode = [[NSDictionary alloc] initWithObjectsAndKeys:@"keyman.png", kTSInputModeAlternateMenuIconFileKey,
[NSNumber numberWithBool:YES], kTSInputModeDefaultStateKey,
[NSNumber numberWithBool:YES], kTSInputModeIsVisibleKey,
@"H", kTSInputModeKeyEquivalentKey,
[NSNumber numberWithInteger:4608], kTSInputModeKeyEquivalentModifiersKey,
[NSNumber numberWithBool:YES], kTSInputModeDefaultStateKey,
@"keyman.png", kTSInputModeMenuIconFileKey,
@"keyman.png", kTSInputModePaletteIconFileKey,
[NSNumber numberWithBool:YES], kTSInputModePrimaryInScriptKey,
@"smUnicodeScript", kTSInputModeScriptKey,
@"hin", @"TISIntendedLanguage", nil];
NSDictionary *modeList = [[NSDictionary alloc] initWithObjectsAndKeys:amhMode, @"com.apple.inputmethod.amh", hinMode, @"com.apple.inputmethod.hin", nil];
NSArray *modeOrder = [[NSArray alloc] initWithObjects:@"com.apple.inputmethod.amh", @"com.apple.inputmethod.hin", nil];
_kmModes = [[NSDictionary alloc] initWithObjectsAndKeys:modeList, kTSInputModeListKey,
modeOrder, kTSVisibleInputModeOrderedArrayKey, nil];
}
return _kmModes;
}
*/
- (NSMenu *)menu {
return self.AppDelegate.menu;
return self.appDelegate.menu;
}
- (KMXFile *)kmx {
return self.AppDelegate.kmx;
return self.appDelegate.kmx;
}
- (void)menuAction:(id)sender {
@ -165,13 +114,15 @@ NSMutableArray *servers;
[self showConfigurationWindow:sender];
}
else if (itag == OSK_MENUITEM_TAG) {
[self.AppDelegate showOSK];
[KMSettingsRepository.shared writeShowOskOnActivate:YES];
os_log_debug([KMLogs oskLog], "menuAction OSK_MENUITEM_TAG, updating settings writeShowOsk to YES");
[self.appDelegate showOSK];
}
else if (itag == ABOUT_MENUITEM_TAG) {
[self.AppDelegate showAboutWindow];
[self.appDelegate showAboutWindow];
}
else if (itag >= KEYMAN_FIRST_KEYBOARD_MENUITEM_TAG) {
[self.AppDelegate selectKeyboardFromMenu:itag];
[self.appDelegate selectKeyboardFromMenu:itag];
}
}
@ -183,7 +134,7 @@ NSMutableArray *servers;
if ([KMOSVersion Version_10_13_1] <= systemVersion && systemVersion <= [KMOSVersion Version_10_13_3]) // between 10.13.1 and 10.13.3 inclusive
{
os_log_info([KMLogs uiLog], "Input Menu: calling workaround instead of showPreferences (sys ver %x)", systemVersion);
[self.AppDelegate showConfigurationWindow]; // call our workaround
[self.appDelegate showConfigurationWindow]; // call our workaround
}
else
{

View file

@ -73,7 +73,6 @@ static const int KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX = 0;
@property (nonatomic, assign) NSEventModifierFlags currentModifierFlags;
@property (nonatomic, assign) CFMachPortRef lowLevelEventTap;
@property (nonatomic, assign) CFRunLoopSourceRef runLoopEventSrc;
@property (nonatomic, assign) BOOL sleeping;
@property (nonatomic, assign) BOOL contextChangedByLowLevelEvent;
@property (nonatomic, strong) OSKWindowController *oskWindow;
@property (nonatomic, strong) NSString *keyboardName;
@ -90,7 +89,6 @@ static const int KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX = 0;
@property (nonatomic, strong) NSString *downloadFilename;
@property (nonatomic, strong) NSMutableData *receivedData;
@property (nonatomic, assign) NSUInteger expectedBytes;
@property (nonatomic, assign) BOOL alwaysShowOSK;
@property (nonatomic, assign) BOOL useVerboseLogging;
@property (nonatomic, assign) BOOL useNullChar;
@property (nonatomic, assign) BOOL debugMode;
@ -102,8 +100,6 @@ static const int KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX = 0;
- (void)showOSK;
- (void)showConfigurationWindow;
- (void)selectKeyboardFromMenu:(NSInteger)tag;
- (void)sleepFollowingDeactivationOfServer:(id)lastServer;
- (void)wakeUpWith:(id)newServer;
- (void)handleKeyEvent:(NSEvent *)event;
- (BOOL)unzipFile:(NSString *)filePath;
- (NSWindowController *)downloadKBWindow_;

View file

@ -7,6 +7,7 @@
//
#import "KMInputMethodAppDelegate.h"
#import "KMInputMethodLifecycle.h"
#import "KMSettingsRepository.h"
#import "KMDataRepository.h"
#import "KMConfigurationWindowController.h"
@ -58,7 +59,6 @@ NSString *const kKeymanKeyboardDownloadCompletedNotification = @"kKeymanKeyboard
@synthesize selectedKeyboard = _selectedKeyboard;
@synthesize activeKeyboards = _activeKeyboards;
@synthesize contextBuffer = _contextBuffer;
@synthesize alwaysShowOSK = _alwaysShowOSK;
id _lastServerWithOSKShowing = nil;
@ -116,6 +116,45 @@ id _lastServerWithOSKShowing = nil;
if (self.runLoopEventSrc && runLoop) {
CFRunLoopAddSource(runLoop, self.runLoopEventSrc, kCFRunLoopDefaultMode);
}
// register to receive notifications generated from KMInputMethodLifecycle
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(inputMethodActivated:) name:kInputMethodActivatedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(inputMethodDeactivated:) name:kInputMethodDeactivatedNotification object:nil];
// start Input Method lifecycle
[KMInputMethodLifecycle.shared startLifecycle];
}
/**
* When the input method is deactivated, hide the OSK and disable the low-level event tap
*/
- (void)inputMethodDeactivated:(NSNotification *)notification {
if ([self.oskWindow.window isVisible]) {
os_log_debug([KMLogs oskLog], "***KMInputMethodAppDelegate inputMethodDeactivated, hiding OSK");
[self.oskWindow.window setIsVisible:NO];
} else {
os_log_debug([KMLogs oskLog], "***KMInputMethodAppDelegate inputMethodDeactivated, OSK already hidden");
}
if (self.lowLevelEventTap) {
os_log_debug([KMLogs lifecycleLog], "***inputMethodDeactivated, disabling event tap");
CGEventTapEnable(self.lowLevelEventTap, NO);
}
}
/**
* When the input method is activated, show the OSK and enable the low-level event tap
*/
- (void)inputMethodActivated:(NSNotification *)notification {
if (self.lowLevelEventTap && !CGEventTapIsEnabled(self.lowLevelEventTap)) {
os_log_debug([KMLogs lifecycleLog], "***KMInputMethodAppDelegate inputMethodActivated, re-enabling event tap...");
CGEventTapEnable(self.lowLevelEventTap, YES);
}
if (_kvk != nil && ([KMInputMethodLifecycle.shared shouldShowOskOnActivate])) {
os_log_debug([KMLogs oskLog], "***KMInputMethodAppDelegate inputMethodActivated, showing OSK");
[self showOSK];
}
}
- (KeymanVersionInfo)versionInfo {
@ -222,43 +261,12 @@ id _lastServerWithOSKShowing = nil;
return (KMInputMethodAppDelegate *)[NSApp delegate];
}
-(void) sleepFollowingDeactivationOfServer:(id)lastServer {
os_log_debug([KMLogs lifecycleLog], "Keyman no longer active IM.");
self.sleeping = YES;
if ([self.oskWindow.window isVisible]) {
os_log_debug([KMLogs oskLog], "sleepFollowingDeactivationOfServer, Hiding OSK.");
// Storing this ensures that if the deactivation is temporary, resulting from dropping down a menu,
// the OSK will re-display when that client application re-activates.
_lastServerWithOSKShowing = lastServer;
[self.oskWindow.window setIsVisible:NO];
}
if (self.lowLevelEventTap) {
os_log_debug([KMLogs lifecycleLog], "sleepFollowingDeactivationOfServer, disabling event tap...");
CGEventTapEnable(self.lowLevelEventTap, NO);
}
}
-(void) wakeUpWith:(id)newServer {
self.sleeping = NO;
if (self.lowLevelEventTap && !CGEventTapIsEnabled(self.lowLevelEventTap)) {
os_log_debug([KMLogs lifecycleLog], "wakeUpWith, Keyman is now the active IM. Re-enabling event tap...");
CGEventTapEnable(self.lowLevelEventTap, YES);
}
// See note in sleepFollowingDeactivationOfServer.
if (_kvk != nil && (_alwaysShowOSK || _lastServerWithOSKShowing == newServer)) {
[self showOSK];
}
_lastServerWithOSKShowing = nil;
}
CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
KMInputMethodAppDelegate *appDelegate = [KMInputMethodAppDelegate AppDelegate];
if (appDelegate != nil) {
if (type == kCGEventTapDisabledByTimeout || type == kCGEventTapDisabledByUserInput) {
// kCGEventTapDisabledByUserInput most likely means we're "sleeping", in which case we want it to stay
// disabled until we get the wake-up call.
if (!appDelegate.sleeping) {
// kCGEventTapDisabledByUserInput most likely means we're "sleeping", in which case we want it to stay disabled until we get the wake-up call.
if ([KMInputMethodLifecycle.shared shouldEnableEventTap]) {
// REVIEW: We might need to consider putting in some kind of counter/flag to ensure that the very next
// event is not another disable so we don't end up in an endless cycle.
os_log([KMLogs eventsLog], "Event tap disabled by %{public}@! Attempting to restart...", (type == kCGEventTapDisabledByTimeout ? @"timeout" : @"user"));
@ -433,11 +441,6 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
return [NSString stringWithFormat:@"%@ - Keyman", _keyboardName];
}
- (void)setAlwaysShowOSK:(BOOL)alwaysShowOSK {
_alwaysShowOSK = alwaysShowOSK;
[[KMSettingsRepository shared] writeAlwaysShowOsk:alwaysShowOSK];
}
- (void)setUseVerboseLogging:(BOOL)useVerboseLogging {
os_log_debug([KMLogs configLog], "Turning verbose logging %{public}@", useVerboseLogging ? @"on." : @"off.");
_debugMode = useVerboseLogging;
@ -447,11 +450,6 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
[[KMSettingsRepository shared] writeUseVerboseLogging:useVerboseLogging];
}
- (BOOL)alwaysShowOSK {
_alwaysShowOSK = [[KMSettingsRepository shared] readAlwaysShowOsk];
return _alwaysShowOSK;
}
- (BOOL)useVerboseLogging {
return [[KMSettingsRepository shared] readUseVerboseLogging];
}
@ -711,7 +709,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
[KMDataRepository.shared createDataDirectoryIfNecessary];
if ([KMSettingsRepository.shared dataMigrationNeeded]) {
BOOL movedData = [KMDataRepository.shared migrateData];
[KMDataRepository.shared migrateData];
[KMSettingsRepository.shared convertSettingsForMigration];
}
@ -887,8 +885,6 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
[self setContextBuffer:nil];
[self setSelectedKeyboard:path];
[self applyPersistedOptions];
if (kvk != nil && self.alwaysShowOSK)
[self showOSK];
}
- (NSArray *)getKmxFilesInKeyboardsDirectory {

View file

@ -0,0 +1,27 @@
/*
* Keyman is copyright (C) SIL International. MIT License.
*
* Created by Shawn Schantz on 2024-09-09.
*
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
extern NSString *const kInputMethodActivatedNotification;
extern NSString *const kInputMethodDeactivatedNotification;
extern NSString *const kInputMethodClientChangeNotification;
@interface KMInputMethodLifecycle : NSObject
+ (KMInputMethodLifecycle *)shared;
+ (NSString*)getClientApplicationId;
- (void)startLifecycle;
- (void)activateClient:(id)client;
- (void)deactivateClient:(id)client;
- (BOOL)shouldEnableEventTap;
- (BOOL)shouldShowOskOnActivate;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,253 @@
/*
* Keyman is copyright (C) SIL International. MIT License.
*
* Created by Shawn Schantz on 2024-09-09.
*
* This class is responsible for determining the state of the Keyman input method.
* It is called from the KMInputController (a subclass of IMKInputController), and
* shares changes in the state of the input method by synchronously posting
* notifications to NSNotificationCenter.
*/
/**
* This class is needed because many activateServer and deactivateServer messages sent from macOS
* to KMInputController, but they are not particularly reliable. Keyman receives some messages when it
* is not active and should not become active. It also receives messages when it is active, but there is no
* need to change state. For example, when a menu is clicked with Keyman active, macOS will send a
* deactivateServer message followed by an activateServer message when the menu is released.
* The messages may also arrive in an unexpected order.
*
* Instead of relying on the information conveyed in these messages, this class interprets them as a notification
* that the input method state may have changed. For the actual state of the input method, it gets the current
* input source using the Carbon APIs TISCopyCurrentKeyboardInputSource and TISGetInputSourceProperty.
* If the result is equal to "keyman.inputmethod.Keyman", then Keyman is the active input method. If, for
* example, the U.S. keyboard were selected, then the result would be "com.apple.keylayout.US".
*
* The state of the text input client is discovered using the NSRunningApplication frontmostApplication API.
* Knowing the current input method and the current text input client enables us to determine whether the
* state has actually changed and how to adjust to the new state.
*
* It is important for state to be known so that the On-screen keyboard can be appropriately shown or hidden
* and the low-level event tap can be stopped or started.
*/
#import "KMInputMethodLifecycle.h"
#import "KMLogs.h"
#import <AppKit/AppKit.h>
#import "KMSettingsRepository.h"
#import <Carbon/Carbon.h>
NSString *const kInputMethodActivatedNotification = @"kInputMethodActivatedNotification";
NSString *const kInputMethodDeactivatedNotification = @"kInputMethodDeactivatedNotification";
NSString *const kInputMethodClientChangeNotification = @"kInputMethodClientChangeNotification";
NSString *const keymanInputMethodName = @"keyman.inputmethod.Keyman";
const double transitionDelay = 0.25;
typedef enum {
Started,
Active,
Inactive
} LifecycleState;
typedef enum {
None,
Activate,
Deactivate,
ChangeClients,
} TransitionType;
@interface KMInputMethodLifecycle()
@property LifecycleState lifecycleState;
@property NSString *inputSourceId;
@property NSString *clientApplicationId;
@end
@implementation KMInputMethodLifecycle
+ (KMInputMethodLifecycle *)shared {
static KMInputMethodLifecycle *shared = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shared = [[KMInputMethodLifecycle alloc] init];
});
return shared;
}
- (instancetype)init {
self = [super init];
if (self) {
_lifecycleState = Started;
_inputSourceId = @"";
_clientApplicationId = @"";
}
return self;
}
/**
* called from Application Delgate during init
*/
- (void)startLifecycle {
_lifecycleState = Started;
}
/**
* Use Carbon APIs to get the current input source or input method. Even though many Carbon APIs were deprecated and removed
* from the OS years ago, these and other low-level APIs are still supported (but apparently completely undocumented).
*/
+ (NSString*)getCurrentInputSourceId {
TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
NSString *inputSourceId = (__bridge NSString *)(TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceID));
return inputSourceId;
}
/**
* Get the bundle ID of the currently active text input client..
*/
+ (NSString*)getClientApplicationId {
NSRunningApplication *currentApp = [[NSWorkspace sharedWorkspace] frontmostApplication];
NSString *clientAppId = [currentApp bundleIdentifier];
return clientAppId;
}
/**
* Based on the current lifecycleState and the input method state from the OS, determine how the state must transition.
*/
- (TransitionType)determineTransition:(NSString*)newInputSourceId withAppId:(NSString*)newClientAppId {
TransitionType transition = None;
BOOL inputSourceIsKeyman = [newInputSourceId isEqualTo:keymanInputMethodName];
BOOL clientHasChanged = [self.clientApplicationId isNotEqualTo:newClientAppId];
os_log_debug([KMLogs lifecycleLog], "determineTransition, current InputSourceId: %{public}@, new InputSourceId: %{public}@, current ClientAppId: %{public}@, new ClientAppId: %{public}@, inputSourceIsKeyman: %d, clientHasChanged: %d", self.inputSourceId, newInputSourceId, self.clientApplicationId, newClientAppId, inputSourceIsKeyman, clientHasChanged);
switch (self.lifecycleState) {
case Started:
transition = Activate;
break;
case Active:
if (inputSourceIsKeyman) {
if (clientHasChanged) {
transition = ChangeClients;
}
} else {
transition = Deactivate;
}
break;
case Inactive:
if (inputSourceIsKeyman) {
transition = Activate;
}
break;
}
return transition;
}
/**
* Update the input method state, consisting of the input source ID and the client application ID.
*/
- (void)saveNewInputMethodState:(NSString*)newInputSourceId withAppId:(NSString*)newClientAppId {
self.inputSourceId = newInputSourceId;
self.clientApplicationId = newClientAppId;
}
/**
* Called when IMKInputController receives an activateServer or a deactivateServer message
*/
- (void)performTransition:(id)client {
NSString *currentInputSource = [KMInputMethodLifecycle getCurrentInputSourceId];
NSString *currentClientAppId = [KMInputMethodLifecycle getClientApplicationId];
TransitionType transition = [self determineTransition:currentInputSource withAppId:currentClientAppId];
[self saveNewInputMethodState:currentInputSource withAppId:currentClientAppId];
switch(transition) {
case None:
os_log_info([KMLogs lifecycleLog], "performTransition: None, new InputSourceId: %{public}@, new application ID: %{public}@", currentInputSource, currentClientAppId);
break;
case Activate:
os_log_info([KMLogs lifecycleLog], "performTransition: Activate, new InputSourceId: %{public}@, new application ID: %{public}@", currentInputSource, currentClientAppId);
/**
* Perform two actions when activating the input method.
* Change the client first which prepares the event handler.
* Then do the activate which starts the event loop and opens the OSK.
*/
[self changeClient];
[self activateInputMethod];
break;
case Deactivate:
os_log_info([KMLogs lifecycleLog], "performTransition: Deactivate, new InputSourceId: %{public}@, new application ID: %{public}@", currentInputSource, currentClientAppId);
[self deactivateInputMethod];
break;
case ChangeClients:
os_log_info([KMLogs lifecycleLog], "performTransition: ChangeClients, new InputSourceId: %{public}@, new application ID: %{public}@", currentInputSource, currentClientAppId);
[self changeClient];
break;
}
}
/**
* Called when IMKInputController receives an activateServer message
*/
- (void)activateClient:(id)client {
os_log_debug([KMLogs lifecycleLog], "KMInputMethodLifecycle activateClient");
[self performSelector:@selector(performTransitionAfterDelay:) withObject:client afterDelay:transitionDelay];
}
/**
* Called when IMKInputController receives an deactivateServer message
*/
- (void)deactivateClient:(id)client {
os_log_debug([KMLogs lifecycleLog], "KMInputMethodLifecycle deactivateClient");
[self performSelector:@selector(performTransitionAfterDelay:) withObject:client afterDelay:transitionDelay];
}
- (void)performTransitionAfterDelay:(id)client {
os_log_debug([KMLogs lifecycleLog], "performTransitionAfterDelay: calling performTransition");
[self performTransition:client];
}
/**
* Change lifecycleState to Active and send notification.
*/
- (void)activateInputMethod {
os_log_debug([KMLogs lifecycleLog], "activateInputMethod");
_lifecycleState = Active;
[[NSNotificationCenter defaultCenter] postNotificationName:kInputMethodActivatedNotification object:self];
}
/**
* Change lifecycleState to Inactive and send notification.
*/
- (void)deactivateInputMethod {
os_log_debug([KMLogs lifecycleLog], "deactivateInputMethod");
_lifecycleState = Inactive;
[[NSNotificationCenter defaultCenter] postNotificationName:kInputMethodDeactivatedNotification object:self];
}
/**
* Does not change lifecycleState, just fires notification so that InputController knows to change the event handler
*/
- (void)changeClient {
os_log_debug([KMLogs lifecycleLog], "changeClient");
[[NSNotificationCenter defaultCenter] postNotificationName:kInputMethodClientChangeNotification object:self];
}
/**
* Returns true if Started or Active
*/
- (BOOL)shouldEnableEventTap {
return ((self.lifecycleState == Started) || (self.lifecycleState == Active));
}
/**
* Returns true if lifecycleState is Active and the Settings require us to show the OSK
*/
- (BOOL)shouldShowOskOnActivate {
return [KMSettingsRepository.shared readShowOskOnActivate]
&& (self.lifecycleState == Active);
}
@end

View file

@ -24,8 +24,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)clearActiveKeyboards;
- (NSDictionary *)readOptionsForSelectedKeyboard;
- (void)writeOptionForSelectedKeyboard:(NSString *)key withValue:(NSString*)value;
- (BOOL)readAlwaysShowOsk;
- (void)writeAlwaysShowOsk:(BOOL)alwaysShowOsk;
- (BOOL)readShowOskOnActivate;
- (void)writeShowOskOnActivate:(BOOL)show;
- (BOOL)readUseVerboseLogging;
- (void)writeUseVerboseLogging:(BOOL)verboseLogging;
@end

View file

@ -14,7 +14,7 @@
NSString *const kActiveKeyboardsKey = @"KMActiveKeyboardsKey";
NSString *const kSelectedKeyboardKey = @"KMSelectedKeyboardKey";
NSString *const kPersistedOptionsKey = @"KMPersistedOptionsKey";
NSString *const kAlwaysShowOSKKey = @"KMAlwaysShowOSKKey";
NSString *const kShowOskOnActivate = @"KMShowOskOnActivate";
NSString *const kUseVerboseLogging = @"KMUseVerboseLogging";
/**
@ -25,6 +25,11 @@ NSString *const kUseVerboseLogging = @"KMUseVerboseLogging";
* represents what it is saving.
*/
NSString *const kKMDeprecatedPersistedOptionsKey = @"KMSavedStoresKey";
/**
* The following constant "KMAlwaysShowOSKKey" is left here for documentation
* but the related UI has been removed according to issue #12342
*/
NSString *const kAlwaysShowOSKKey = @"KMAlwaysShowOSKKey";
NSString *const kObsoletePathComponent = @"/Documents/Keyman-Keyboards";
NSString *const kNewPathComponent = @"/Library/Application Support/keyman.inputmethod.Keyman/";
@ -306,14 +311,14 @@ NSInteger const kCurrentDataModelVersionNumber = kVersionStoreDataInLibraryDirec
}
}
- (BOOL)readAlwaysShowOsk {
- (BOOL)readShowOskOnActivate {
NSUserDefaults *userData = [NSUserDefaults standardUserDefaults];
return [userData boolForKey:kAlwaysShowOSKKey];
return [userData boolForKey:kShowOskOnActivate];
}
- (void)writeAlwaysShowOsk:(BOOL)alwaysShowOsk {
- (void)writeShowOskOnActivate:(BOOL)show {
NSUserDefaults *userData = [NSUserDefaults standardUserDefaults];
[userData setBool:alwaysShowOsk forKey:kAlwaysShowOSKKey];
[userData setBool:show forKey:kShowOskOnActivate];
}
- (BOOL)readUseVerboseLogging {

View file

@ -9,7 +9,7 @@
#import <Cocoa/Cocoa.h>
#import <KeymanEngine4Mac/KeymanEngine4Mac.h>
@interface OSKWindowController : NSWindowController
@interface OSKWindowController : NSWindowController <NSWindowDelegate>
@property (nonatomic, weak) IBOutlet OSKView *oskView;

View file

@ -8,6 +8,8 @@
#import "OSKWindowController.h"
#import "KMInputMethodAppDelegate.h"
#import "KMInputMethodLifecycle.h"
#import "KMSettingsRepository.h"
#import "KMLogs.h"
@interface OSKWindowController ()
@ -25,7 +27,6 @@
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self stopTimer];
}
@ -55,7 +56,6 @@
- (void)windowDidLoad {
os_log_debug([KMLogs oskLog], "OSKWindowController windowDidLoad");
[super windowDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidResize:) name:NSWindowDidResizeNotification object:self.window];
[self.oskView setKvk:[self.AppDelegate kvk]];
[self startTimerWithTimeInterval:0.1];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
@ -66,6 +66,11 @@
[self.oskView resizeOSKLayout];
}
- (void)windowWillClose:(NSNotification *)notification {
[KMSettingsRepository.shared writeShowOskOnActivate:NO];
os_log_debug([KMLogs oskLog], "OSKWindowController windowWillClose, updating settings writeShowOsk to NO");
}
- (void)helpAction:(id)sender {
NSString *kvkPath = [self AppDelegate].kvk.filePath;
if (!kvkPath)

View file

@ -2,7 +2,7 @@
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22689"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22690"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -14,7 +14,7 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Keyman" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" animationBehavior="default" id="F0z-JX-Cv5">
<window title="Keyman" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" frameAutosaveName="KeymanOsk" animationBehavior="default" id="F0z-JX-Cv5">
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="614" height="212"/>