spiegel-keyman/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m
2025-01-31 09:44:08 +07:00

187 lines
6.6 KiB
Objective-C

/*
* 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"
#import "KMSentryHelper.h"
@implementation KMInputController
KMInputMethodEventHandler* _eventHandler;
- (KMInputMethodAppDelegate *)appDelegate {
return (KMInputMethodAppDelegate *)[NSApp delegate];
}
- (id)initWithServer:(IMKServer *)server delegate:(id)delegate client:(id)inputClient
{
os_log_debug([KMLogs lifecycleLog], "+++KMInputController initWithServer, active app: '%{public}@'", [KMInputMethodLifecycle getRunningApplicationId]);
self = [super initWithServer:server delegate:delegate client:inputClient];
if (self) {
os_log_debug([KMLogs lifecycleLog], " +++initWithServer, self: %p", self);
}
return self;
}
- (NSUInteger)recognizedEvents:(id)sender {
return NSEventMaskKeyDown | NSEventMaskFlagsChanged;
}
- (BOOL)handleEvent:(NSEvent *)event client:(id)sender {
if (event == nil || sender == nil || self.kmx == nil || _eventHandler == nil) {
os_log_error([KMLogs eventsLog], "IMInputController handleEvent: not prepared to handle event = %{public}@", event);
return NO; // Not sure this can ever happen.
}
return [_eventHandler handleEvent:event client:sender];
}
// Passthrough from the app delegate low level event hook
// to the input method event handler for handleBackspace.
- (void)handleBackspace:(NSEvent *)event {
os_log_debug([KMLogs keyLog], "KMInputController handleBackspace, event = %{public}@", event);
if(_eventHandler != nil) {
[_eventHandler handleBackspace:event];
}
}
/**
* Called by the app delegate when KMInputMethodLifecycle determines the user has switched clients
*/
- (void)changeClients:(NSString *)clientAppId {
os_log_debug([KMLogs lifecycleLog], "***KMInputController changeClients, deactivate old eventHandler and activate new one");
[self deactivateEventHandler];
[self activateEventHandler:clientAppId];
}
/**
* Called by the app delegate when KMInputMethodLifecycle determines the user has deactivated Keyman
*/
- (void)deactivateClient {
os_log_debug([KMLogs lifecycleLog], "***KMInputController deactivateClient, deactivate old eventHandler");
[self deactivateEventHandler];
}
- (void)deactivateEventHandler {
if (_eventHandler != nil) {
os_log_debug([KMLogs lifecycleLog], " * KMInputController deactivate old eventHandler");
[_eventHandler deactivate];
}
}
- (void)activateEventHandler:(NSString *)clientAppId {
os_log_debug([KMLogs lifecycleLog], " * KMInputController activate new eventHandler");
_eventHandler = [[KMInputMethodEventHandler alloc] initWithClient:clientAppId client:self.client];
}
/**
* Called by the OS to activate the input method, but some calls are not useful and are followed milliseconds later
* by a deactivateServer. These short-lived activations may be generated by clicking and releasing menu items without changing applications.
* Rather than treat every message received as a true activation, we send a message to KMInputMethodLifeCycle to evaluate.
*/
- (void)activateServer:(id)sender {
os_log_info([KMLogs lifecycleLog], " +++KMInputController, activateServer, self=%p", self);
[sender overrideKeyboardWithKeyboardNamed:@"com.apple.keylayout.US"];
/*
When this KMInputController becomes the active server for the input method,
then immediately update the AppDelegate. The duration that this controller is the
active server may be extremely short, but, if so, we will receive another
call to activateServer moments later and can update the AppDelegate again.
*/
[self attachToAppDelegate];
/*
Call the shared lifecycle object so it can evaluate the current state
and determine whether this is
1) a real activation of the input method or
2) a change in clients or
3) a false alarm
*/
[KMInputMethodLifecycle.shared activateClient:sender];
}
- (void)attachToAppDelegate {
self.appDelegate.inputController = self;
}
/**
* Called by the OS to deactivate the input method
*/
- (void)deactivateServer:(id)sender {
os_log_info([KMLogs lifecycleLog], " +++KMInputController, deactivateServer, self=%p", self);
/*
Call the shared lifecycle object so it can evaluate the current state
and determine whether this is
1) a real deactivation of the input method or
2) a change in clients or
3) a false alarm
*/
[KMInputMethodLifecycle.shared deactivateClient:sender];
}
- (NSMenu *)menu {
return self.appDelegate.menu;
}
- (KMXFile *)kmx {
return self.appDelegate.kmx;
}
- (void)menuAction:(id)sender {
NSMenuItem *mItem = [sender objectForKey:kIMKCommandMenuItemName];
NSInteger itag = mItem.tag;
os_log_debug([KMLogs uiLog], "Keyman menu clicked - tag: %lu", itag);
if (itag == CONFIG_MENUITEM_TAG) {
[KMSentryHelper addUserBreadCrumb:@"menu" message:@"Configuration..."];
[self showConfigurationWindow:sender];
}
else if (itag == OSK_MENUITEM_TAG) {
[KMSentryHelper addUserBreadCrumb:@"menu" message:@"On-screen Keyboard"];
[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) {
[KMSentryHelper addUserBreadCrumb:@"menu" message:@"About"];
[self.appDelegate showAboutWindow];
}
else if (itag >= KEYMAN_FIRST_KEYBOARD_MENUITEM_TAG) {
[KMSentryHelper addUserBreadCrumb:@"menu" message:@"Selected Keyboard"];
[self.appDelegate selectKeyboardFromMenu:itag];
}
}
- (void)showConfigurationWindow:(id)sender {
// Using `showConfigurationWindow` instead of `showPreferences:` because `showPreferences:` is missing in
// High Sierra (10.13.1 - 10.13.3). See: https://bugreport.apple.com/web/?problemID=35422518
// rrb: where Apple's API is broken (10.13.1-10.13.3) call our workaround, otherwise, call showPreferences
u_int16_t systemVersion = [KMOSVersion SystemVersion];
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
}
else
{
os_log_info([KMLogs uiLog], "Input Menu: calling Apple's showPreferences (sys ver %x)", systemVersion);
[self showPreferences:sender]; // call Apple API
}
}
@end