chore(developer): Merge branch 'beta' into fix/developer/remove-unused-keyboard-info-compiler-messages

This commit is contained in:
Dr Mark C. Sinclair 2024-03-21 09:46:40 +00:00
commit ad568fccc6
28 changed files with 223 additions and 622 deletions

View file

@ -1,5 +1,18 @@
# Keyman Version History
## 17.0.291 beta 2024-03-20
* chore(mac): clean up code obsoleted by core (#10877)
* fix(linux): Replace deprecated `pkg_resources` module with `packaging.version` (#10860)
## 17.0.290 beta 2024-03-19
* (#10956)
## 17.0.289 beta 2024-03-16
* fix(common): add unit test for --debug flag for builder (#10974)
## 17.0.288 beta 2024-03-13
* test(developer): kmc keyboard info compiler messages unit tests (#10848)

View file

@ -1 +1 @@
17.0.289
17.0.292

View file

@ -287,9 +287,16 @@ function setKeymanContext(text, doSync, selStart, selLength) {
let selEnd = selStart + selLength;
selEnd = isNaN(selEnd) ? undefined : selEnd;
const shouldReset = keyman.context.updateContext(text, selStart, selEnd);
if(!doSync || shouldReset) {
if(doSync) {
const shouldReset = keyman.context.updateContext(text, selStart, selEnd);
if(shouldReset) {
keyman.resetContext();
}
} else {
// if not in "sync" mode, we have a hard context reset; just full-reset it.
keyman.context.setText(text);
keyman.context.setSelection(selStart, selStart+selLength);
keyman.resetContext();
}
}

View file

@ -30,6 +30,7 @@ Build-Depends:
python3-lxml,
python3-magic,
python3-numpy,
python3-packaging,
python3-pil,
python3-pip,
python3-qrcode,
@ -83,13 +84,14 @@ Package: python3-keyman-config
Section: python
Architecture: all
Depends:
dbus-x11,
dconf-cli,
gir1.2-webkit2-4.1 | gir1.2-webkit2-4.0,
keyman-engine,
python3-bs4,
python3-gi,
python3-packaging,
python3-sentry-sdk (>= 1.1),
dbus-x11,
${misc:Depends},
${python3:Depends},
Description: Keyman for Linux configuration

View file

@ -3,8 +3,8 @@
import json
import logging
import os
import packaging.version
import zipfile
from pkg_resources import parse_version
from enum import Enum
from shutil import rmtree
@ -149,7 +149,7 @@ class InstallKmp():
fileVersion = secure_lookup(system, 'fileVersion')
if not fileVersion:
fileVersion = '7.0'
if parse_version(fileVersion) > parse_version(__version__):
if packaging.version.parse(fileVersion) > packaging.version.parse(__version__):
logging.error("install_kmp.py: error: %s requires a newer version of Keyman (%s)",
inputfile, fileVersion)
rmtree(self.packageDir)

View file

@ -10,6 +10,7 @@ import subprocess
import sys
import tempfile
import webbrowser
import packaging.version
import gi
@ -20,8 +21,6 @@ except ValueError:
# TODO: Remove once we drop support for Ubuntu 20.04 Focal
gi.require_version('WebKit2', '4.0')
from pkg_resources import parse_version
from gi.repository import Gtk, WebKit2
from keyman_config import _, secure_lookup
@ -134,11 +133,12 @@ class InstallKmpWindow(Gtk.Dialog):
self.checkcontinue = False
def _uninstall_prev_version_if_necessary(self, keyboardid, viewkmp, installed_kmp_ver, info):
if not installed_kmp_ver or not secure_lookup(info, 'version', 'description'):
new_kmp_version = secure_lookup(info, 'version', 'description')
if not installed_kmp_ver or not new_kmp_version:
return True
logging.info("package version %s", secure_lookup(info, 'version', 'description'))
logging.info("package version %s", new_kmp_version)
logging.info("installed kmp version %s", installed_kmp_ver)
if parse_version(secure_lookup(info, 'version', 'description')) >= parse_version(installed_kmp_ver):
if packaging.version.parse(new_kmp_version) >= packaging.version.parse(installed_kmp_ver):
return True
response = self._show_version_message_dlg(
viewkmp,

View file

@ -3,7 +3,7 @@
import json
import logging
import os
from pkg_resources import parse_version
import packaging.version
from keyman_config import secure_lookup
from keyman_config.deprecated_decorator import deprecated
@ -133,7 +133,7 @@ def _get_kmp_version_internal(packageId, location, previousVersion):
kmp = get_installed_kmp(location)
if packageId in kmp:
version = kmp[packageId]['kmpversion']
if not previousVersion or parse_version(previousVersion) < parse_version(version):
if not previousVersion or packaging.version.parse(previousVersion) < packaging.version.parse(version):
previousVersion = version
return previousVersion

View file

@ -4,12 +4,12 @@ import argparse
import datetime
import logging
import os
import requests
import requests_cache
import sys
import time
from pkg_resources import parse_version
from zipfile import is_zipfile
import packaging.version
import requests
import requests_cache
from keyman_config import KeymanApiUrl, add_standard_arguments, initialize_logging, initialize_sentry, secure_lookup
from keyman_config.fcitx_util import is_fcitx_running
@ -181,7 +181,7 @@ def main():
sys.exit(3)
pkg_version = secure_lookup(pkgdata, 'version')
if installed_kmp_ver and pkg_version:
if parse_version(installed_kmp_ver) > parse_version(pkg_version):
if packaging.version.parse(installed_kmp_ver) > packaging.version.parse(pkg_version):
if args.force:
logging.warning(f'km-package-install: warning: The {args.package} keyboard ' +
f'is already installed with a newer version {installed_kmp_ver}. ' +

View file

@ -17,8 +17,17 @@ setup(
zip_safe=False, # don't install a zip
install_requires=[
'lxml', 'numpy', 'Pillow', 'requests', 'requests-cache',
'python-magic', 'qrcode', 'sentry-sdk'
'dbus-python',
'lxml',
'numpy',
'packaging',
'pillow',
'PyGObject',
'python-magic',
'qrcode',
'requests-cache',
'requests',
'sentry-sdk',
],
# metadata to display on PyPI

View file

@ -75,18 +75,14 @@
E213601E2142D7C000A043B7 /* keyman-88.png in Resources */ = {isa = PBXBuildFile; fileRef = E213601D2142D7BF00A043B7 /* keyman-88.png */; };
E21799051FC5B7BC00F2D66A /* KMInputMethodEventHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = E21799041FC5B7BC00F2D66A /* KMInputMethodEventHandler.m */; };
E21E645820C04EA8000D6274 /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = E21E645720C04EA7000D6274 /* logo.png */; };
E22020F6200506F300B74FAC /* KMInputMethodSafariClientEventHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = E22020F5200506F300B74FAC /* KMInputMethodSafariClientEventHandler.m */; };
E23380FB21407AA100B90591 /* SILInBlue76.png in Resources */ = {isa = PBXBuildFile; fileRef = E23380FA21407AA100B90591 /* SILInBlue76.png */; };
E240F599202DED740000067D /* KMPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = E240F598202DED740000067D /* KMPackage.m */; };
E24C79EA1FFFCC7500D8E46F /* KMInputMethodBrowserClientEventHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = E24C79E91FFFCC7500D8E46F /* KMInputMethodBrowserClientEventHandler.m */; };
E2585A7F20DD6C3C00CBB994 /* KMMethodEventHandlerTests.kmx in Resources */ = {isa = PBXBuildFile; fileRef = E2585A7E20DD6C3C00CBB994 /* KMMethodEventHandlerTests.kmx */; };
E2585A8120DD7CF100CBB994 /* TestAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E2585A8020DD7CF100CBB994 /* TestAppDelegate.m */; };
E27BA9D32020D8BF00D273E7 /* SimpleTest.kmx in Resources */ = {isa = PBXBuildFile; fileRef = E27BA9D22020D8BF00D273E7 /* SimpleTest.kmx */; };
E28914D32024D1B80048E71E /* KeymanEngine4Mac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 984C2B211A79DF1B0023F89D /* KeymanEngine4Mac.framework */; };
E28914D52024E3DF0048E71E /* KMInputMethodBrowserClientEventHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = E24C79E91FFFCC7500D8E46F /* KMInputMethodBrowserClientEventHandler.m */; };
E28914D62024E4010048E71E /* KMInputMethodEventHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = E21799041FC5B7BC00F2D66A /* KMInputMethodEventHandler.m */; };
E28914D72024E6220048E71E /* KeymanEngine4Mac.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 984C2B211A79DF1B0023F89D /* KeymanEngine4Mac.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E29F22F4201A59B1005EA234 /* KMInputMethodBrowserClientEventHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E29F22F3201A59B1005EA234 /* KMInputMethodBrowserClientEventHandlerTests.m */; };
E2D5529D20DD8E0200D77CFB /* LegacyTestClient.m in Sources */ = {isa = PBXBuildFile; fileRef = E2D5529C20DD8E0200D77CFB /* LegacyTestClient.m */; };
F9B9814A51575496B5DA2948 /* Pods_KeymanTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A71FD8BA8404531DFBD861B6 /* Pods_KeymanTests.framework */; };
/* End PBXBuildFile section */
@ -357,22 +353,15 @@
E21799031FC5B74D00F2D66A /* KMInputMethodEventHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMInputMethodEventHandler.h; sourceTree = "<group>"; };
E21799041FC5B7BC00F2D66A /* KMInputMethodEventHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMInputMethodEventHandler.m; sourceTree = "<group>"; };
E21E645720C04EA7000D6274 /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = logo.png; path = Keyman4MacIM/KMConfiguration/logo.png; sourceTree = SOURCE_ROOT; };
E22020F3200505EF00B74FAC /* KMInputMethodSafariClientEventHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMInputMethodSafariClientEventHandler.h; sourceTree = "<group>"; };
E22020F5200506F300B74FAC /* KMInputMethodSafariClientEventHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMInputMethodSafariClientEventHandler.m; sourceTree = "<group>"; };
E22020F920050D6300B74FAC /* KMInputMethodBrowserClientEventHandlerProtected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMInputMethodBrowserClientEventHandlerProtected.h; sourceTree = "<group>"; };
E23380FA21407AA100B90591 /* SILInBlue76.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = SILInBlue76.png; path = Keyman4MacIM/Images/SILInBlue76.png; sourceTree = SOURCE_ROOT; };
E240F597202DED300000067D /* KMPackage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMPackage.h; sourceTree = "<group>"; };
E240F598202DED740000067D /* KMPackage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMPackage.m; sourceTree = "<group>"; };
E24C79E91FFFCC7500D8E46F /* KMInputMethodBrowserClientEventHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMInputMethodBrowserClientEventHandler.m; sourceTree = "<group>"; };
E24C79EB1FFFCE3000D8E46F /* KMInputMethodBrowserClientEventHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMInputMethodBrowserClientEventHandler.h; sourceTree = "<group>"; };
E24C79EC1FFFEA6B00D8E46F /* KMInputMethodEventHandlerProtected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMInputMethodEventHandlerProtected.h; sourceTree = "<group>"; };
E2585A7E20DD6C3C00CBB994 /* KMMethodEventHandlerTests.kmx */ = {isa = PBXFileReference; lastKnownFileType = file; path = KMMethodEventHandlerTests.kmx; sourceTree = "<group>"; };
E2585A8020DD7CF100CBB994 /* TestAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestAppDelegate.m; sourceTree = "<group>"; };
E2585A8220DD7D3F00CBB994 /* TestAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestAppDelegate.h; sourceTree = "<group>"; };
E27BA9CD2020322000D273E7 /* OCMock.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OCMock.framework; path = ../Carthage/Build/Mac/OCMock.framework; sourceTree = "<group>"; };
E27BA9D22020D8BF00D273E7 /* SimpleTest.kmx */ = {isa = PBXFileReference; lastKnownFileType = file; path = SimpleTest.kmx; sourceTree = "<group>"; };
E29F22F1201A59B1005EA234 /* KeymanTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KeymanTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
E29F22F3201A59B1005EA234 /* KMInputMethodBrowserClientEventHandlerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMInputMethodBrowserClientEventHandlerTests.m; sourceTree = "<group>"; };
E29F22F5201A59B2005EA234 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
E2C92D4720B46CEA00742A7D /* KeymanEngine4Mac.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = KeymanEngine4Mac.framework.dSYM; path = Keyman4MacIM/Frameworks/KeymanEngine4Mac.framework.dSYM; sourceTree = "<group>"; };
E2D5529B20DD8D6100D77CFB /* LegacyTestClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LegacyTestClient.h; sourceTree = "<group>"; };
@ -568,12 +557,6 @@
E21799041FC5B7BC00F2D66A /* KMInputMethodEventHandler.m */,
298D09F62A1F4533006B9DFE /* TextApiCompliance.h */,
298D09F52A1F4533006B9DFE /* TextApiCompliance.m */,
E24C79EC1FFFEA6B00D8E46F /* KMInputMethodEventHandlerProtected.h */,
E22020F920050D6300B74FAC /* KMInputMethodBrowserClientEventHandlerProtected.h */,
E24C79EB1FFFCE3000D8E46F /* KMInputMethodBrowserClientEventHandler.h */,
E22020F3200505EF00B74FAC /* KMInputMethodSafariClientEventHandler.h */,
E24C79E91FFFCC7500D8E46F /* KMInputMethodBrowserClientEventHandler.m */,
E22020F5200506F300B74FAC /* KMInputMethodSafariClientEventHandler.m */,
98E672961B532DE000DBDE2F /* KMKeyboardHelpWindow */,
989C9C141A7876DE00A20425 /* MainMenu.xib */,
984B8F3E1AF1C2F400E096A8 /* OnScreenKeyboard */,
@ -689,7 +672,6 @@
children = (
E2585A7E20DD6C3C00CBB994 /* KMMethodEventHandlerTests.kmx */,
E27BA9D22020D8BF00D273E7 /* SimpleTest.kmx */,
E29F22F3201A59B1005EA234 /* KMInputMethodBrowserClientEventHandlerTests.m */,
E29F22F5201A59B2005EA234 /* Info.plist */,
E2585A8220DD7D3F00CBB994 /* TestAppDelegate.h */,
E2585A8020DD7CF100CBB994 /* TestAppDelegate.m */,
@ -997,8 +979,6 @@
9836B3721AE5F11D00780482 /* unzip.c in Sources */,
296FE2FC275DD21600F46898 /* KMPackageReader.m in Sources */,
9832472E1A9EABC70010B90C /* KMConfigColumn1CellView.m in Sources */,
E22020F6200506F300B74FAC /* KMInputMethodSafariClientEventHandler.m in Sources */,
E24C79EA1FFFCC7500D8E46F /* KMInputMethodBrowserClientEventHandler.m in Sources */,
297A501928DF4D360074EB1B /* PrivacyConsent.m in Sources */,
98FE105E1B4DE8F400525F54 /* NSWindow+SuppMethods.m in Sources */,
9836B3741AE5F11D00780482 /* ZipArchive.mm in Sources */,
@ -1022,8 +1002,6 @@
2999C69929D3E4CE006366F5 /* InputMethodTests.m in Sources */,
E211769D20E182DD00F8065D /* NoContextTestClient.m in Sources */,
E2D5529D20DD8E0200D77CFB /* LegacyTestClient.m in Sources */,
E29F22F4201A59B1005EA234 /* KMInputMethodBrowserClientEventHandlerTests.m in Sources */,
E28914D52024E3DF0048E71E /* KMInputMethodBrowserClientEventHandler.m in Sources */,
E21176A020E18C5200F8065D /* AppleCompliantTestClient.m in Sources */,
E2585A8120DD7CF100CBB994 /* TestAppDelegate.m in Sources */,
E28914D62024E4010048E71E /* KMInputMethodEventHandler.m in Sources */,
@ -1424,7 +1402,7 @@
"@loader_path/../Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.7;
MACOSX_DEPLOYMENT_TARGET = 10.10;
PRODUCT_BUNDLE_IDENTIFIER = org.sil.consoleTestbed.KeymanTests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1458,7 +1436,7 @@
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.7;
MACOSX_DEPLOYMENT_TARGET = 10.10;
PRODUCT_BUNDLE_IDENTIFIER = org.sil.consoleTestbed.KeymanTests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View file

@ -8,8 +8,6 @@
#import "KMInputController.h"
#import "KMInputMethodEventHandler.h"
#import "KMInputMethodBrowserClientEventHandler.h"
#import "KMInputMethodSafariClientEventHandler.h"
#import "KMOSVersion.h"
#include <Carbon/Carbon.h> /* For kVK_ constants. */
@ -83,22 +81,6 @@ NSMutableArray *servers;
NSLog(@"activateServer, new active app: '%@', sender %@", clientAppId, sender);
}
// TODO: uncomment? commented the browser cases to see how they work with core code
/*
// Most things in Safari work well using the normal way, but Google Docs doesn't.
if ([clientAppId isEqual: @"com.apple.Safari"]) {
_eventHandler = [[KMInputMethodSafariClientEventHandler alloc] init];
}
else if ([clientAppId isEqual: @"org.mozilla.firefox"] ||
[clientAppId isEqual: @"com.google.Chrome"]) {
_eventHandler = [[KMInputMethodBrowserClientEventHandler alloc] init];
}
else {
// We cache the client for use with events sourced from the low level tap
// where we don't necessarily have any access to the current client.
_eventHandler = [[KMInputMethodEventHandler alloc] initWithClient:clientAppId client:sender];
}
*/
_eventHandler = [[KMInputMethodEventHandler alloc] initWithClient:clientAppId client:sender];
}

View file

@ -335,9 +335,14 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
// of events)
if(sysEvent.keyCode == kVK_Delete && appDelegate.inputController != nil) {
NSLog(@"Event tap handling kVK_Delete.");
[appDelegate.inputController handleBackspace:sysEvent];
[appDelegate.inputController handleBackspace:sysEvent];
} else if(sysEvent.keyCode == kVK_Delete) {
NSLog(@"Event tap not handling kVK_Delete, appDelegate.inputController = %@", appDelegate.inputController);
}
if(sysEvent.keyCode == 255) {
NSLog(@"*** kKeymanEventKeyCode = 0xFF");
} else {
NSLog(@"Event tap not handling kVK_Delete, appDelegate.inputController = %@", appDelegate.inputController);
NSLog(@"*** other: %d(%x)", (char) sysEvent.keyCode, sysEvent.keyCode);
}
switch(sysEvent.keyCode) {
@ -443,7 +448,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
return;
}
// TODO pass array instead of making repeated calls
// TODO: pass array instead of making repeated calls
for (NSString *key in persistedOptionsForSelectedKeyboard) {
NSString *value = [persistedOptionsForSelectedKeyboard objectForKey:key];
NSLog(@"persisted options found in UserDefaults for keyboard %@, key: %@, value: %@", _selectedKeyboard, key, value);

View file

@ -1,20 +0,0 @@
//
// KMInputMethodBrowserClientEventHandler.h
// Keyman4MacIM
// This class is used for browser clients because certain javascript-based websites, such as Google Docs) do
// not properly deal with calls to insertText calls that replace a range of characters. So for these "legacy"
// apps, we post one or more deletes (i.e., backspace), followed by a special code that tells us we're now
// ready to insert the composed text.
//
// Created by tom on 1/5/18.
// Copyright © 2018 SIL International. All rights reserved.
//
#ifndef KMInputMethodBrowserClientEventHandler_h
#define KMInputMethodBrowserClientEventHandler_h
#import "KMInputMethodEventHandler.h"
@interface KMInputMethodBrowserClientEventHandler : KMInputMethodEventHandler
@end
#endif /* KMInputMethodBrowserClientEventHandler_h */

View file

@ -1,108 +0,0 @@
//
// KMInputMethodBrowserClientEventHandler.m
// Keyman
//
// Created by tom on 1/5/18.
// Copyright © 2018 SIL International. All rights reserved.
//
#import "KMInputMethodBrowserClientEventHandler.h"
#import "KMInputMethodEventHandlerProtected.h"
#import "KMInputMethodBrowserClientEventHandlerProtected.h"
@implementation KMInputMethodBrowserClientEventHandler
// Because Google Docs can't report its context in any of the browsers (Safari, Chrome, Firefox) and Word sometimes
// has trouble doing this in Chrome, we want to try to detect it so each browser can respond appropriately.
NSUInteger _failuresToRetrieveExpectedContext;
- (instancetype)init {
return [self initWithLegacyMode:YES clientSelectionCanChangeUnexpectedly:YES];
}
- (instancetype)initWithLegacyMode:(BOOL)legacy clientSelectionCanChangeUnexpectedly:(BOOL) flagClientSelectionCanChangeUnexpectedly {
self = [super initWithLegacyMode:legacy clientSelectionCanChangeUnexpectedly: flagClientSelectionCanChangeUnexpectedly];
if (self) {
_failuresToRetrieveExpectedContext = 0;
}
return self;
}
- (void)checkContextIn:(id) client {
if (!self.willDeleteNullChar && !self.contextOutOfDate && _failuresToRetrieveExpectedContext < 3) {
if (self.appDelegate.debugMode) {
NSLog(@"Checking to see if we're in Google Docs (or some other site that can't give context).");
}
NSUInteger bufferLength = self.contextBuffer.length;
if (bufferLength) {
NSUInteger location = [client selectedRange].location;
if (location != NSNotFound && location > 0) {
if ([self appDelegate].debugMode)
NSLog(@"Trying to get context up to location %lu", location);
NSString* clientContext = [self getLimitedContextFrom:client at:location];
if (clientContext == nil)
{
// Client is failing to provide useful response to attributedSubstringFromRange.
// Word (in MS Live) occasionally does this.
[self setInSiteThatDoesNotGiveContext];
}
else if (!clientContext.length) {
if ([self appDelegate].debugMode) {
NSLog(@"Expected context = '%@'", self.contextBuffer);
NSLog(@"Actual clientContext was empty");
}
_failuresToRetrieveExpectedContext++;
}
else
{
unichar lastCodepointExpected = [self.contextBuffer characterAtIndex:bufferLength - 1];
unichar lastCodepointInClient = [clientContext characterAtIndex:clientContext.length - 1];
if (lastCodepointExpected != lastCodepointInClient) {
if ([self appDelegate].debugMode) {
NSLog(@"Expected context = '%@'", self.contextBuffer);
NSLog(@"Actual clientContext = '%@'", (clientContext == nil ? @"{nil}" : clientContext));
NSLog(@"Last character expected (in contextBuffer) = '%lu'", (unsigned long)lastCodepointExpected);
NSLog(@"Last character in clientContext = '%lu'", (unsigned long)lastCodepointInClient);
}
// MS Word converts/reports plain spaces (32) as non-breaking spaces (160). If we
// get this kind of mismatch, we don't want to count this as a definite match, but
// we also don't want to count it as a failure to match. So just wait for a more
// reliable character to test.
if (lastCodepointInClient != 160 || lastCodepointExpected != 32) {
_failuresToRetrieveExpectedContext++;
}
}
else {
if ([self appDelegate].debugMode) {
NSLog(@"We got what we were expecting from the client. We can stop checking.");
}
_failuresToRetrieveExpectedContext = NSUIntegerMax;
}
}
}
else {
if ([self appDelegate].debugMode) {
NSLog(@"bufferLength is %lu, but location was %lu.", bufferLength, location);
}
_failuresToRetrieveExpectedContext++;
}
if (_failuresToRetrieveExpectedContext == 3)
[self setInSiteThatDoesNotGiveContext];
}
}
}
- (void)setInSiteThatDoesNotGiveContext {
if ([self appDelegate].debugMode) {
NSLog(@"Detected some editor (e.g., Google Docs) that can't provide context.");
}
_failuresToRetrieveExpectedContext = NSUIntegerMax;
if (self.clientSelectionCanChangeUnexpectedly) {
self.clientCanProvideSelectionInfo = Unreliable;
self.clientSelectionCanChangeUnexpectedly = NO; // This isn't true (it can change unexpectedly), but we can't get the context, so we pretend/hope it won't.
}
}
@end

View file

@ -1,18 +0,0 @@
//
// KMInputMethodBrowserClientEventHandlerProtected.h
// Keyman4MacIM
//
// Created by tom on 1/9/18.
// Copyright © 2018 SIL International. All rights reserved.
//
#ifndef KMInputMethodEventBrowserClientHandlerProtected_h
#define KMInputMethodEventBrowserClientHandlerProtected_h
@interface KMInputMethodBrowserClientEventHandler ()
- (void)setInSiteThatDoesNotGiveContext;
@end
#endif /* KMInputMethodEventBrowserClientHandlerProtected_h */

View file

@ -6,19 +6,17 @@
// Copyright © 2017-2018 SIL International. All rights reserved.
//
#import "KMInputMethodEventHandler.h"
#import "KMInputMethodEventHandlerProtected.h"
#import <KeymanEngine4Mac/KeymanEngine4Mac.h>
#import <Carbon/Carbon.h> /* For kVK_ constants. */
#import "KeySender.h"
#import "TextApiCompliance.h"
@import Sentry;
@interface KMInputMethodEventHandler ()
@property (nonatomic, retain) KeySender* keySender;
@property (nonatomic, retain) TextApiCompliance* apiCompliance;
@property NSUInteger generatedBackspaceCount;
@property BOOL backspaceHandledLowLevel;
@property NSUInteger lowLevelBackspaceCount;
@property (nonatomic, retain) NSString* clientApplicationId;
@property NSString *queuedText;
@property BOOL contextChanged;
@ -30,8 +28,6 @@ const CGKeyCode kProcessPendingBuffer = 0xFF;
const NSUInteger kMaxContext = 80;
const NSTimeInterval kQueuedTextEventDelayInSeconds = 0.1;
//_pendingBuffer contains text that is ready to be sent to the client when all delete-backs are finished.
NSMutableString* _pendingBuffer;
CGKeyCode _keyCodeOfOriginalEvent;
CGEventSourceRef _sourceFromOriginalEvent = nil;
CGEventSourceRef _sourceForGeneratedEvent = nil;
@ -39,32 +35,13 @@ NSMutableString* _easterEggForSentry = nil;
NSString* const kEasterEggText = @"Sentry force now";
NSString* const kEasterEggKmxName = @"EnglishSpanish.kmx";
NSRange _previousSelRange;
// Protected initializer for use by subclasses
- (instancetype)initWithLegacyMode:(BOOL)legacy clientSelectionCanChangeUnexpectedly:(BOOL) flagClientSelectionCanChangeUnexpectedly {
self = [super init];
if (self) {
_previousSelRange = NSMakeRange(NSNotFound, NSNotFound);
_clientSelectionCanChangeUnexpectedly = flagClientSelectionCanChangeUnexpectedly;
_clientCanProvideSelectionInfo = Unknown;
_legacyMode = NO;
_contextOutOfDate = YES;
if (legacy) {
[self switchToLegacyMode];
}
}
return self;
}
// This is the public initializer.
- (instancetype)initWithClient:(NSString *)clientAppId client:(id) sender {
self.senderForDeleteBack = sender;
_keySender = [[KeySender alloc] init];
_clientApplicationId = clientAppId;
_generatedBackspaceCount = 0;
//BOOL legacy = [self isClientAppLegacy:clientAppId];
_lowLevelBackspaceCount = 0;
_queuedText = nil;
// In Xcode, if Keyman is the active IM and is in "debugMode" and "English plus Spanish" is
// the current keyboard and you type "Sentry force now", it will force a simulated crash to
@ -76,16 +53,7 @@ NSRange _previousSelRange;
else
_easterEggForSentry = nil;
// For the Atom editor, this isn't really true (the context CAN change unexpectedly), but we can't get
// the context, so we pretend/hope it won't.
BOOL selectionCanChangeUnexpectedly = (![clientAppId isEqual: @"com.github.atom"]);
return [self initWithLegacyMode:self.apiCompliance.mustBackspaceUsingEvents clientSelectionCanChangeUnexpectedly:selectionCanChangeUnexpectedly];
}
- (void)switchToLegacyMode {
_legacyMode = YES;
if ([self.appDelegate debugMode])
NSLog(@"Using legacy mode for this app.");
return self;
}
- (void)deactivate {
@ -121,39 +89,12 @@ NSRange _previousSelRange;
// situation where a user could reasonably hope that any dead-keys typed before using a
// command shortcut would be remembered, so other than an insignificant performance penalty,
// the only downside to treating all commands as having the potential to change the selection
// is that some "legacy" apps can't get their context at all. This can be overridden so that
// legacy apps can mitigate this problem as appropriate.
// is that some non-compliant apps can't get their context at all. This can be overridden so that
// non-compliant apps can mitigate this problem as appropriate.
[self.appDelegate logDebugMessage:@"KMInputMethodEventHandler handleCommand, event type=%@, must refresh context", event.type];
self.contextChanged = YES;
}
- (void)checkContextIn:(id)client {
// Base implementation is no-op
}
- (void)replaceExistingSelectionIn:(id)client with:(NSString *) text {
[client insertText:text replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
_previousSelRange.location += text.length;
_previousSelRange.length = 0;
}
- (void)insertPendingBufferTextIn:(id)client {
NSUInteger length = [self pendingBuffer].length;
if (!length) {
[self.appDelegate logDebugMessage:@"Error - expected text in pending buffer!"];
return;
}
NSString* text = [self pendingBuffer];
[self.appDelegate logDebugMessage:@"Inserting text from pending buffer: \"%@\"", text];
[client insertText:text replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
_previousSelRange.location += text.length;
_previousSelRange.length = 0;
[self setPendingBuffer:@""];
}
- (KMInputMethodAppDelegate *)appDelegate {
return (KMInputMethodAppDelegate *)[NSApp delegate];
}
@ -162,54 +103,6 @@ NSRange _previousSelRange;
return self.appDelegate.kme;
}
- (NSMutableString *)contextBuffer {
if (_contextOutOfDate) {
NSException* exception = [NSException
exceptionWithName:@"InvalidOperationException"
reason:@"Attempt to access out-of-date context."
userInfo:nil];
@throw exception;
}
return self.appDelegate.contextBuffer;
}
-(NSString *)getLimitedContextFrom:(id)sender at:(NSUInteger) len {
if (![sender respondsToSelector:@selector(attributedSubstringFromRange:)])
return nil;
NSUInteger start = 0;
if (len > kMaxContext) {
[self.appDelegate logDebugMessage:@"Limiting context to %lu characters", kMaxContext];
start = len - kMaxContext;
len = kMaxContext;
}
NSString *preBuffer = [[sender attributedSubstringFromRange:NSMakeRange(start, len)] string];
[self.appDelegate logDebugMessage:@"preBuffer = \"%@\"", preBuffer ? preBuffer : @"nil"];
if (preBuffer.length)
[self.appDelegate logDebugMessage:@"First character: '%x'", [preBuffer characterAtIndex:0]];
else
[self.appDelegate logDebugMessage:@"preBuffer has a length of 0"];
return preBuffer;
}
// Return the pending buffer. If it is NIL create it.
-(NSMutableString*)pendingBuffer;
{
if ( _pendingBuffer == nil ) {
_pendingBuffer = [[NSMutableString alloc] init];
}
return _pendingBuffer;
}
// Change the pending buffer.
-(void)setPendingBuffer:(NSString*)string
{
NSMutableString* buffer = [self pendingBuffer];
[buffer setString:string];
}
- (BOOL) handleEventWithKeymanEngine:(NSEvent *)event in:(id) sender {
[self.appDelegate logDebugMessage:@"handleEventWithKeymanEngine, event = %@", event];
CoreKeyOutput *output = nil;
@ -265,7 +158,7 @@ NSRange _previousSelRange;
}
/**
handleBackspace: handles generated for a subset of non-compliant apps (such as Adobe apps) that do
handleBackspace: handles generated backspaces for a subset of non-compliant apps (such as Adobe apps) that do
not support replacing text with the insertText API but also intercept events so that we do not see them in handleEvent.
Other apps, such as Word, show the event both in the low
level EventTap and in the normal IM handleEvent. Thus, we set a flag after
@ -275,16 +168,14 @@ NSRange _previousSelRange;
*/
- (void)handleBackspace:(NSEvent *)event {
[self.appDelegate logDebugMessage:@"KMInputMethodEventHandler handleBackspace, event = %@", event];
NSLog(@" backspaceHandledLowLevel: %d, generatedBackspaceCount: %lu", _backspaceHandledLowLevel, (unsigned long)_generatedBackspaceCount);
self.backspaceHandledLowLevel = NO;
if (self.generatedBackspaceCount > 0) {
self.generatedBackspaceCount--;
self.backspaceHandledLowLevel = YES;
self.lowLevelBackspaceCount++;
// if we just encountered the last backspace, then send event to insert queued text
if (self.generatedBackspaceCount == 0) {
[self performSelector:@selector(triggerInsertQueuedText:) withObject:event afterDelay:kQueuedTextEventDelayInSeconds];
if ((self.generatedBackspaceCount == 0) && (self.queuedText.length > 0)) {
[self performSelector:@selector(triggerInsertQueuedText:) withObject:event afterDelay:kQueuedTextEventDelayInSeconds];
}
}
}
@ -294,28 +185,6 @@ NSRange _previousSelRange;
[self.keySender sendKeymanKeyCodeForEvent:event];
}
- (void)initiatePendingBufferProcessing:(id)sender {
[self postKeyPressToFrontProcess:kProcessPendingBuffer from:NULL];
}
- (void)postKeyPressToFrontProcess:(CGKeyCode)code from:(CGEventSourceRef) source {
ProcessSerialNumber psn;
GetFrontProcess(&psn);
if ([self.appDelegate debugMode]) {
if (code == kProcessPendingBuffer) {
NSLog(@"Posting code to tell Keyman to process characters in pending buffer.");
}
else {
NSLog(@"Posting a keypress (down/up) to the 'front process' for the %hu key.", code);
}
}
[self.appDelegate postKeyboardEventWithSource:source code:code postCallback:^(CGEventRef eventToPost) {
CGEventPostToPSN((ProcessSerialNumberPtr)&psn, eventToPost);
}];
}
//MARK: Core-related key processing
- (void)checkTextApiCompliance:(id)client {
@ -358,16 +227,17 @@ NSRange _previousSelRange;
// indicates that our generated backspace event(s) are consumed
// and we can insert text that followed the backspace(s)
if (event.keyCode == kKeymanEventKeyCode) {
[self.appDelegate logDebugMessage:@"handleEvent, handling kKeymanEventKeyCode"];
[self.appDelegate logDebugMessage:@"handleEvent, handling kKeymanEventKeyCode"];
[self insertQueuedText: event client:sender];
return YES;
}
// for some apps, handleEvent will not be called for the generated backspace
// but if it is, we need to let it pass through rather than process again in core
if((event.keyCode == kVK_Delete) && (self.backspaceHandledLowLevel)) {
if((event.keyCode == kVK_Delete) && (self.lowLevelBackspaceCount > 0)) {
[self.appDelegate logDebugMessage:@"handleEvent, allowing generated backspace to pass through"];
self.backspaceHandledLowLevel = NO;
self.lowLevelBackspaceCount--;
// return NO to pass through to client app
return NO;
}
@ -482,18 +352,35 @@ NSRange _previousSelRange;
} else if (output.isDeleteOnlyScenario) {
if ((event.keyCode == kVK_Delete) && output.codePointsToDeleteBeforeInsert == 1) {
// let the delete pass through in the original event rather than sending a new delete
[self.appDelegate logDebugMessage:@"KXMInputMethodHandler applyOutputToTextInputClient, delete only scenario"];
[self.appDelegate logDebugMessage:@"KXMInputMethodHandler applyOutputToTextInputClient, delete only scenario with passthrough"];
handledEvent = NO;
} else {
[self.appDelegate logDebugMessage:@"KXMInputMethodHandler applyOutputToTextInputClient, delete only scenario"];
[self sendEvents:event forOutput:output];
}
} else if (output.isDeleteAndInsertScenario) {
// TODO: fix issue #10246
/*
// needs further testing to fix backspace bug in google docs (without breaking other apps)
// assume that all non-compliant apps which require backspaces apply an extra backspace if the original event is a backspace
if ((self.apiCompliance.mustBackspaceUsingEvents) && (event.keyCode == kVK_Delete)) {
output.codePointsToDeleteBeforeInsert--;
os_log_with_type(keymanLog, OS_LOG_TYPE_INFO, "isDeleteAndInsertScenario, after delete pressed subtracting one backspace to reach %d and insert text '%{public}@'", output.codePointsToDeleteBeforeInsert, output.textToInsert);
if (output.codePointsToDeleteBeforeInsert == 0) {
// no backspace events needed
[self insertAndReplaceTextForOutput:output client:client];
} else {
// still need at least one backspace event
[self sendEvents:event forOutput:output];
}
}
*/
if (self.apiCompliance.mustBackspaceUsingEvents) {
[self.appDelegate logDebugMessage:@"KXMInputMethodHandler applyOutputToTextInputClient, delete and insert scenario with events"];
[self sendEvents:event forOutput:output];
} else {
[self.appDelegate logDebugMessage:@"KXMInputMethodHandler applyOutputToTextInputClient, delete and insert scenario with insert API"];
[self.appDelegate logDebugMessage:@"KXMInputMethodHandler applyOutputToTextInputClient, delete and insert scenario with insert API"];
// directly insert text and handle backspaces by using replace
[self insertAndReplaceTextForOutput:output client:client];
}
@ -585,15 +472,16 @@ NSRange _previousSelRange;
}
-(void)sendEvents:(NSEvent *)event forOutput:(CoreKeyOutput*)output {
[self.appDelegate logDebugMessage:@"sendEvents called"];
[self.appDelegate logDebugMessage:@"sendEvents called, output = %@", output];
_sourceForGeneratedEvent = CGEventCreateSourceFromEvent([event CGEvent]);
self.generatedBackspaceCount = output.codePointsToDeleteBeforeInsert;
if (output.hasCodePointsToDelete) {
for (int i = 0; i < output.codePointsToDeleteBeforeInsert; i++) {
[self.appDelegate logDebugMessage:@"sendEvents queueing backspace"];
[self.appDelegate logDebugMessage:@"sendEvents sending backspace key event"];
[self.keySender sendBackspaceforEventSource:_sourceForGeneratedEvent];
}
}
@ -611,7 +499,7 @@ NSRange _previousSelRange;
[self insertAndReplaceText:self.queuedText deleteCount:0 client:client];
self.queuedText = nil;
} else {
[self.appDelegate logDebugMessage:@"handleQueuedText called but no text to insert"];
[self.appDelegate logDebugMessage:@"insertQueuedText called but no text to insert"];
}
}

View file

@ -1,56 +0,0 @@
//
// KMInputMethodEventHandlerProtected.h
// Keyman4MacIM
//
// Created by tom on 1/5/18.
// Copyright © 2018 SIL International. All rights reserved.
//
#ifndef KMInputMethodEventHandlerProtected_h
#define KMInputMethodEventHandlerProtected_h
typedef NS_ENUM(NSInteger, ClientCapability) {
Unknown,
Yes,
No,
Unreliable
};
@interface KMInputMethodEventHandler ()
//@property (nonatomic) NSMutableDictionary *kbData;
//@property (nonatomic) NSDictionary *kmModes;
@property (assign) BOOL willDeleteNullChar;
@property (assign) BOOL contextOutOfDate;
// This flag indicates which mode is being used for replacing already typed text when composing characters
// (i.e., when not using deadkeys). Some apps (and some javascript-based websites, such as Google Docs) do
// not properly deal with calls to insertText calls that replace a range of characters. So for these "legacy"
// apps, we post one or more deletes (i.e., backspace), followed by a special code that tells us we're now
// ready to insert the composed text.
@property BOOL legacyMode;
// Some clients (e.g. Chrome) handle the mouse down events before we get a crack at them. For such apps that
// are able to report their current selection location (LibreOffice can't even do that!), we can do some
// checking at the start of the event processing to see if we're probably still in the same place where we
// left off previously.
@property (assign) BOOL clientSelectionCanChangeUnexpectedly; // REVIEW: Maybe we can get notification from these clients by handling mouseDownOnCharacterIndex.
@property (assign) ClientCapability clientCanProvideSelectionInfo;
@property id senderForDeleteBack;
@property BOOL ignoreNextDeleteBackHighLevel;
- (instancetype)initWithLegacyMode:(BOOL)legacy clientSelectionCanChangeUnexpectedly:(BOOL) flagClientSelectionCanChangeUnexpectedly;
- (void)handleCommand:(NSEvent *)event;
// This just sets the legacyMode property to true and spits out a debug message to that effect.
- (void)switchToLegacyMode;
- (void)checkContextIn:(id)client;
- (void)replaceExistingSelectionIn:(id)client with:(NSString *) text;
- (void)insertPendingBufferTextIn:(id)client;
- (KMInputMethodAppDelegate *)appDelegate;
- (NSMutableString *)contextBuffer;
- (NSString *)getLimitedContextFrom:(id)sender at:(NSUInteger) len;
// Return the pending buffer. If it is NIL create it.
-(NSMutableString*)pendingBuffer;
-(void)setPendingBuffer:(NSString*)string;
- (void)initiatePendingBufferProcessing:(id)sender;
@end
#endif /* KMInputMethodEventHandlerProtected_h */

View file

@ -1,17 +0,0 @@
//
// KMInputMethodSafariClientEventHandler.h
// Keyman4MacIM
// This class is used for the Safari browser to get specific behavior needed to improve behavior for some sites.
//
// Created by tom on 1/9/18.
// Copyright © 2018 SIL International. All rights reserved.
//
#ifndef KMInputMethodSafariClientEventHandler_h
#define KMInputMethodSafariClientEventHandler_h
#import "KMInputMethodBrowserClientEventHandler.h"
@interface KMInputMethodSafariClientEventHandler : KMInputMethodBrowserClientEventHandler
@end
#endif /* KMInputMethodSafariClientEventHandler_h */

View file

@ -1,86 +0,0 @@
//
// KMInputMethodSafariClientEventHandler.m
// Keyman
//
// Created by tom on 1/9/18.
// Copyright © 2018 SIL International. All rights reserved.
//
#import "KMInputMethodSafariClientEventHandler.h"
#import "KMInputMethodEventHandlerProtected.h"
#import "KMInputMethodBrowserClientEventHandlerProtected.h"
@implementation KMInputMethodSafariClientEventHandler
// Because Google Docs can't report its context, if we detect that we are in Google Docs, we change to legacy mode.
//BOOL _explicitlyDeleteExistingSelectionBeforeInserting = NO;
BOOL _preserveContextForNextCmdA;
- (instancetype)init {
self = [super initWithLegacyMode:NO clientSelectionCanChangeUnexpectedly:NO];
if (self) {
_preserveContextForNextCmdA = NO;
//_explicitlyDeleteExistingSelectionBeforeInserting = YES;
}
return self;
}
- (void)handleCommand:(NSEvent *)event {
// If Safari issued a Cmd-A to remove the selection, we know what the
// context should be, so keep it.
if ([event.characters isEqualTo:@"a"] && _preserveContextForNextCmdA)
_preserveContextForNextCmdA = NO;
else
[super handleCommand:event];
}
- (void)setInSiteThatDoesNotGiveContext {
[self switchToLegacyMode];
}
- (void)replaceExistingSelectionIn:(id)client with:(NSString *) text {
// The following commented out code is the code that would make it possible to type over an
// existing selection in Word in Safari, but I don't know of a way to distinguish that case
// from other contexts in Safari (wherein this code inserts an extra leading space).
// if (_explicitlyDeleteExistingSelectionBeforeInserting) {
// NSUInteger selLength = [sender selectedRange].length;
// if (selLength > 0 && selLength != NSNotFound) {
// if ([self.AppDelegate debugMode]) {
// NSLog(@"Attempting to delete existing selection of length = %lu by replacing it with a space (which should not actually appear in the text).", selLength);
// }
//
// [sender insertText:@" " replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
//
// if ([self.AppDelegate debugMode])
// NSLog(@"Re-posting original (unhandled) code: %d", (int)_keyCodeOfOriginalEvent);
//
// CGEventSourceRef sourceFromEvent = CGEventCreateSourceFromEvent([event CGEvent]);
// [self postKeyPressToFrontProcess:event.keyCode from:sourceFromEvent];
// CFRelease(sourceFromEvent);
// return YES;
// }
// }
[super replaceExistingSelectionIn:client with:text];
// In Google Docs (i.e., "legacy mode" when there is an existing selection, the inserted characters
// stay selected. The following clears the selection.
if (self.legacyMode) {
if ([self.appDelegate debugMode])
NSLog(@"Sending Command-Shift-A to clear selection in Google Docs");
_preserveContextForNextCmdA = YES;
ProcessSerialNumber psn;
GetFrontProcess(&psn);
CGEventRef event = CGEventCreateKeyboardEvent(NULL, kVK_ANSI_A, true);
//set shift and command keys down for above event
CGEventSetFlags(event, kCGEventFlagMaskShift | kCGEventFlagMaskCommand);
CGEventPostToPSN(&psn, event);
CFRelease(event);
event = CGEventCreateKeyboardEvent(NULL, kVK_ANSI_A, false);
CGEventSetFlags(event, kCGEventFlagMaskShift | kCGEventFlagMaskCommand);
CGEventPostToPSN(&psn, event);
CFRelease(event);
}
}
@end

View file

@ -29,33 +29,6 @@ const CGKeyCode kKeymanEventKeyCode = 0xFF;
return self;
}
- (void)sendKeyDown:(NSUInteger)keyCode forSourceEvent:(NSEvent *)event includeKeyUp:(BOOL)includeKeyUpEvent {
// Returns the frontmost app, which is the app that receives key events.
NSRunningApplication *app = NSWorkspace.sharedWorkspace.frontmostApplication;
pid_t processId = app.processIdentifier;
NSString *bundleId = app.bundleIdentifier;
[self.appDelegate logDebugMessage:@"sendKeyDown keyCode %lu to app %@ with pid %d", (unsigned long)keyCode, bundleId, processId];
if (keyCode < 0x100) {
CGEventRef cgevent = [event CGEvent];
// use source from event to generate new event
CGEventSourceRef source = CGEventCreateSourceFromEvent(cgevent);
CGEventRef keyDownEvent = CGEventCreateKeyboardEvent(source, (CGKeyCode)keyCode, true);
// TODO: add version check
CGEventPostToPid(processId, keyDownEvent);
CFRelease(keyDownEvent);
if (includeKeyUpEvent) {
CGEventRef keyUpEvent = CGEventCreateKeyboardEvent(source, (CGKeyCode)keyCode, false);
CGEventPostToPid(processId, keyUpEvent);
CFRelease(keyUpEvent);
}
}
}
- (void)sendBackspaceforEventSource:(CGEventSourceRef)eventSource {
[self.appDelegate logDebugMessage:@"KeySender sendBackspaceforEventSource"];
@ -90,23 +63,20 @@ const CGKeyCode kKeymanEventKeyCode = 0xFF;
- (void)sendKeymanKeyCodeForEvent:(NSEvent *)event {
[self.appDelegate logDebugMessage:@"KeySender sendKeymanKeyCodeForEvent"];
[self sendKeyDown:kKeymanEventKeyCode forSourceEvent:event includeKeyUp:NO];
ProcessSerialNumber psn;
// Returns the frontmost app, which is the app that receives key events.
NSRunningApplication *app = NSWorkspace.sharedWorkspace.frontmostApplication;
pid_t processId = app.processIdentifier;
NSString *bundleId = app.bundleIdentifier;
GetProcessForPID(processId, &psn);
[self.appDelegate logDebugMessage:@"sendKeymanKeyCodeForEvent keyCode %lu to app %@ with pid %d", (unsigned long)kKeymanEventKeyCode, bundleId, processId];
CGEventRef cgevent = [event CGEvent];
// use source from event to generate new event
CGEventSourceRef source = CGEventCreateSourceFromEvent(cgevent);
CGEventRef keyDownEvent = CGEventCreateKeyboardEvent(source, kKeymanEventKeyCode, true);
// TODO: add version check
CGEventPostToPid(processId, keyDownEvent);
// use nil as source, as this generated event is not directly tied to the originating event
CGEventRef keyDownEvent = CGEventCreateKeyboardEvent(nil, kKeymanEventKeyCode, true);
CGEventPostToPSN(&psn, keyDownEvent);
CFRelease(keyDownEvent);
// this is not a real keycode, so we do not need a key up event

View file

@ -11,7 +11,6 @@
#import <XCTest/XCTest.h>
#import "KMInputMethodEventHandler.h"
#import "KMInputMethodEventHandlerProtected.h"
#import "LegacyTestClient.h"
#import "AppleCompliantTestClient.h"
#import "TextApiCompliance.h"

View file

@ -1,58 +0,0 @@
//
// KMInputMethodBrowserClientEventHandlerTests.m
// KeymanTests
//
// Created by tom on 1/25/18.
// Copyright © 2018 SIL International. All rights reserved.
//
#import <XCTest/XCTest.h>
#import "TestAppDelegate.h"
#import "KMInputMethodBrowserClientEventHandler.h"
#import "KMInputMethodEventHandlerProtected.h"
#import <OCMock/OCMock.h>
@interface KeymanTests : XCTestCase
@property (nonatomic, strong) TestAppDelegate * delegate;
@end
@implementation KeymanTests
KMInputMethodBrowserClientEventHandler * _im;
- (void)setUp {
[super setUp];
_delegate = [[TestAppDelegate alloc] init];
[[NSApplication sharedApplication] setDelegate:_delegate];
// Put setup code here. This method is called before the invocation of each test method in the class.
NSString *path = [[NSBundle bundleForClass:[KeymanTests class]] pathForResource:@"SimpleTest.kmx" ofType:nil];
KMXFile *kmxFile = [[KMXFile alloc] initWithFilePath:path];
[_delegate setKmx:kmxFile];
_im = [[KMInputMethodBrowserClientEventHandler alloc] init];
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
- (void)skip_TestcheckContextIn_EmptyContextExpected_NoChangeToclientSelectionCanChangeUnexpectedly {
// If the expected context is empty, no attempt should be made to check for matching context from
// client, so even though the client would also return an empty string, calling checkContextIn
// repeatedly has no effect (i.e., it does not set clientSelectionCanChangeUnexpectedly to false).
// Therefore a subsequent key down event *should* result in a call to the client to inquire about
// the current selection.
id client = OCMStrictProtocolMock(@protocol(IMKTextInput));
OCMStub([client selectedRange]).andReturn(NSMakeRange(0, 0));
NSAttributedString *attrSubstring = [[NSAttributedString alloc] init];
OCMStub([client attributedSubstringFromRange:(NSMakeRange(0, 0))]).andReturn(attrSubstring);
OCMExpect([client insertText:@"a" replacementRange:NSMakeRange(NSNotFound, NSNotFound)]);
for (int i = 0; i < 8; i++)
{
[_im checkContextIn:client];
}
NSEvent *event = [_delegate keyStrokeEventForCharacter: @"a" keyCode:kVK_ANSI_A];
[_im handleEvent:event client:client];
[client verify];
}
@end

View file

@ -0,0 +1,28 @@
#!/usr/bin/env bash
## START STANDARD BUILD SCRIPT INCLUDE
# adjust relative paths as necessary
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "${THIS_SCRIPT%/*}/../../../../../resources/build/build-utils.sh"
# END STANDARD BUILD SCRIPT INCLUDE
builder_describe \
"Tests child builds debug flag" \
@dep build
function do_build() {
# Test the debug flag
builder_is_debug_build || builder_die "FAIL: child: expecting builder_is_debug_build to be true"
echo "PASS: child: builder_is_debug_build is true"
builder_has_option --debug || builder_die "FAIL: child: expecting builder_has_option --debug to be true"
echo "PASS: child: builder_has_option --debug is true"
}
builder_parse "$@"
# Sanity Check: verify that we are running as a child (and not a dep) build
! builder_is_dep_build || builder_die "FAIL: child: builder_is_dep_build should be false"
builder_is_child_build || builder_die "FAIL: child: builder_is_child_build should be true"
builder_run_action build do_build

View file

@ -0,0 +1,27 @@
#!/usr/bin/env bash
## START STANDARD BUILD SCRIPT INCLUDE
# adjust relative paths as necessary
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "${THIS_SCRIPT%/*}/../../../../../../resources/build/build-utils.sh"
# END STANDARD BUILD SCRIPT INCLUDE
builder_describe \
"Tests child/dependency builds debug flag" \
configure build
function do_build() {
# Test the debug flag
builder_is_debug_build || builder_die "FAIL: child/dep: expecting builder_is_debug_build to be true"
echo "PASS: child/dep: builder_is_debug_build is true"
builder_has_option --debug || builder_die "FAIL: child/dep: expecting builder_has_option --debug to be true"
echo "PASS: child/dep: builder_has_option --debug is true"
}
builder_parse "$@"
# Sanity Check: verify that we are running as a dep (and not a child) build
builder_is_dep_build || builder_die "FAIL: child/dep: builder_is_dep_build should be true"
! builder_is_child_build || builder_die "FAIL: child/dep: builder_is_child_build should be false"
builder_run_action build do_build

View file

@ -0,0 +1,27 @@
#!/usr/bin/env bash
## START STANDARD BUILD SCRIPT INCLUDE
# adjust relative paths as necessary
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "${THIS_SCRIPT%/*}/../../../../../resources/build/build-utils.sh"
# END STANDARD BUILD SCRIPT INCLUDE
builder_describe \
"Tests dependency builds debug flag" \
configure build
function do_build() {
# Test the debug flag
builder_is_debug_build || builder_die "FAIL: dep: expecting builder_is_debug_build to be true"
echo "PASS: dep: builder_is_debug_build is true"
builder_has_option --debug || builder_die "FAIL: dep: expecting builder_has_option --debug to be true"
echo "PASS: dep: builder_has_option --debug is true"
}
builder_parse "$@"
# Sanity Check: verify that we are running as a dep (and not a child) build
builder_is_dep_build || builder_die "FAIL: dep: builder_is_dep_build should be true"
! builder_is_child_build || builder_die "FAIL: dep: builder_is_child_build should be false"
builder_run_action build do_build

View file

@ -0,0 +1,22 @@
#!/usr/bin/env bash
## START STANDARD BUILD SCRIPT INCLUDE
# adjust relative paths as necessary
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "${THIS_SCRIPT%/*}/../../../../resources/build/build-utils.sh"
# END STANDARD BUILD SCRIPT INCLUDE
builder_describe \
"Tests child and dependency builds --debug flag" \
@dep :child build
function do_build() {
builder_is_debug_build || builder_die "FAIL: parent: expecting builder_is_debug_build to be true"
echo "PASS: parent: builder_is_debug_build is true"
builder_has_option --debug || builder_die "FAIL: parent: expecting builder_has_option --debug to be true"
echo "PASS: parent: builder_has_option --debug is true"
}
builder_parse build:child -d
builder_run_action build do_build
builder_run_child_actions build

View file

@ -174,6 +174,7 @@ echo -e "${COLOR_BLUE}## Running dependency tests${COLOR_RESET}"
"$THIS_SCRIPT_PATH/builder-deps.test.sh"
"$THIS_SCRIPT_PATH/dependencies/test.sh"
"$THIS_SCRIPT_PATH/trees/test.sh"
"$THIS_SCRIPT_PATH/debug-deps/test.sh"
echo -e "${COLOR_BLUE}## End external tests${COLOR_RESET}"
echo

View file

@ -60,18 +60,24 @@ export class ContextHost extends Mock {
updateContext(text: string, selStart: number, selEnd: number): boolean {
let shouldResetContext = false;
let tempMock = new Mock(text, selStart ?? text._kmwLength(), selEnd ?? text._kmwLength());
let newLeft = tempMock.getTextBeforeCaret();
let oldLeft = this.getTextBeforeCaret();
if(text != this.text) {
let tempMock = new Mock(text, selStart ?? text._kmwLength(), selEnd ?? text._kmwLength());
let newLeft = tempMock.getTextBeforeCaret();
let oldLeft = this.getTextBeforeCaret();
let unexpectedBeforeCharCount = findCommonSubstringEndIndex(newLeft, oldLeft, true) + 1;
shouldResetContext = !!unexpectedBeforeCharCount;
}
if(shouldResetContext) {
this.text = text;
this.selStart = selStart;
this.selEnd = selEnd;
} else {
// Transform selection coordinates to their location within the longform context window.
let delta = oldLeft._kmwLength() - newLeft._kmwLength();
this.selStart = selStart - delta;
this.selEnd = selEnd - delta;
}
if(selStart === undefined || selEnd === undefined) {