diff --git a/HISTORY.md b/HISTORY.md index fe669d5609..ef43dd0bc8 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -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) diff --git a/VERSION.md b/VERSION.md index 065d23ebd9..8f31495ca0 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.289 \ No newline at end of file +17.0.292 \ No newline at end of file diff --git a/ios/engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/ios-host.js b/ios/engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/ios-host.js index 621e643bb9..d5eb798b67 100644 --- a/ios/engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/ios-host.js +++ b/ios/engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/ios-host.js @@ -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(); } } diff --git a/linux/debian/control b/linux/debian/control index a1f1844dd1..8f49b92ea0 100644 --- a/linux/debian/control +++ b/linux/debian/control @@ -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 diff --git a/linux/keyman-config/keyman_config/install_kmp.py b/linux/keyman-config/keyman_config/install_kmp.py index 06399c5304..d44a84cb97 100755 --- a/linux/keyman-config/keyman_config/install_kmp.py +++ b/linux/keyman-config/keyman_config/install_kmp.py @@ -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) diff --git a/linux/keyman-config/keyman_config/install_window.py b/linux/keyman-config/keyman_config/install_window.py index 686e24eac9..dda242c7e1 100755 --- a/linux/keyman-config/keyman_config/install_window.py +++ b/linux/keyman-config/keyman_config/install_window.py @@ -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, diff --git a/linux/keyman-config/keyman_config/list_installed_kmp.py b/linux/keyman-config/keyman_config/list_installed_kmp.py index 2b6a32acc4..2bac9c8d4e 100755 --- a/linux/keyman-config/keyman_config/list_installed_kmp.py +++ b/linux/keyman-config/keyman_config/list_installed_kmp.py @@ -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 diff --git a/linux/keyman-config/km-package-install b/linux/keyman-config/km-package-install index 0dff66c47a..4e6e7e0099 100755 --- a/linux/keyman-config/km-package-install +++ b/linux/keyman-config/km-package-install @@ -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}. ' + diff --git a/linux/keyman-config/setup.py b/linux/keyman-config/setup.py index 8931241ff2..58fdbbaaca 100644 --- a/linux/keyman-config/setup.py +++ b/linux/keyman-config/setup.py @@ -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 diff --git a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj index 8574f5c6da..cf83375ba8 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj +++ b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj @@ -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 = ""; }; E21799041FC5B7BC00F2D66A /* KMInputMethodEventHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMInputMethodEventHandler.m; sourceTree = ""; }; 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 = ""; }; - E22020F5200506F300B74FAC /* KMInputMethodSafariClientEventHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMInputMethodSafariClientEventHandler.m; sourceTree = ""; }; - E22020F920050D6300B74FAC /* KMInputMethodBrowserClientEventHandlerProtected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMInputMethodBrowserClientEventHandlerProtected.h; sourceTree = ""; }; 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 = ""; }; E240F598202DED740000067D /* KMPackage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMPackage.m; sourceTree = ""; }; - E24C79E91FFFCC7500D8E46F /* KMInputMethodBrowserClientEventHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMInputMethodBrowserClientEventHandler.m; sourceTree = ""; }; - E24C79EB1FFFCE3000D8E46F /* KMInputMethodBrowserClientEventHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMInputMethodBrowserClientEventHandler.h; sourceTree = ""; }; - E24C79EC1FFFEA6B00D8E46F /* KMInputMethodEventHandlerProtected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMInputMethodEventHandlerProtected.h; sourceTree = ""; }; E2585A7E20DD6C3C00CBB994 /* KMMethodEventHandlerTests.kmx */ = {isa = PBXFileReference; lastKnownFileType = file; path = KMMethodEventHandlerTests.kmx; sourceTree = ""; }; E2585A8020DD7CF100CBB994 /* TestAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestAppDelegate.m; sourceTree = ""; }; E2585A8220DD7D3F00CBB994 /* TestAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestAppDelegate.h; sourceTree = ""; }; E27BA9CD2020322000D273E7 /* OCMock.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OCMock.framework; path = ../Carthage/Build/Mac/OCMock.framework; sourceTree = ""; }; E27BA9D22020D8BF00D273E7 /* SimpleTest.kmx */ = {isa = PBXFileReference; lastKnownFileType = file; path = SimpleTest.kmx; sourceTree = ""; }; 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 = ""; }; E29F22F5201A59B2005EA234 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E2C92D4720B46CEA00742A7D /* KeymanEngine4Mac.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = KeymanEngine4Mac.framework.dSYM; path = Keyman4MacIM/Frameworks/KeymanEngine4Mac.framework.dSYM; sourceTree = ""; }; E2D5529B20DD8D6100D77CFB /* LegacyTestClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LegacyTestClient.h; sourceTree = ""; }; @@ -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 = ""; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m index 9b6ee2f5b2..8ba6966dab 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m @@ -8,8 +8,6 @@ #import "KMInputController.h" #import "KMInputMethodEventHandler.h" -#import "KMInputMethodBrowserClientEventHandler.h" -#import "KMInputMethodSafariClientEventHandler.h" #import "KMOSVersion.h" #include /* 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]; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 301164db1a..b17034116f 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -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); diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodBrowserClientEventHandler.h b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodBrowserClientEventHandler.h deleted file mode 100644 index f04233322b..0000000000 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodBrowserClientEventHandler.h +++ /dev/null @@ -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 */ diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodBrowserClientEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodBrowserClientEventHandler.m deleted file mode 100644 index efef3e4aef..0000000000 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodBrowserClientEventHandler.m +++ /dev/null @@ -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 diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodBrowserClientEventHandlerProtected.h b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodBrowserClientEventHandlerProtected.h deleted file mode 100644 index faaf606546..0000000000 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodBrowserClientEventHandlerProtected.h +++ /dev/null @@ -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 */ diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m index d5d82ab6e0..2ebda0470a 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m @@ -6,19 +6,17 @@ // Copyright © 2017-2018 SIL International. All rights reserved. // #import "KMInputMethodEventHandler.h" -#import "KMInputMethodEventHandlerProtected.h" #import #import /* 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"]; } } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandlerProtected.h b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandlerProtected.h deleted file mode 100644 index b224f7bf50..0000000000 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandlerProtected.h +++ /dev/null @@ -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 */ diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodSafariClientEventHandler.h b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodSafariClientEventHandler.h deleted file mode 100644 index b65236ec83..0000000000 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodSafariClientEventHandler.h +++ /dev/null @@ -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 */ diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodSafariClientEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodSafariClientEventHandler.m deleted file mode 100644 index 838913b9a7..0000000000 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodSafariClientEventHandler.m +++ /dev/null @@ -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 diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KeySender.m b/mac/Keyman4MacIM/Keyman4MacIM/KeySender.m index 9c1bcead13..74092ea26f 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KeySender.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KeySender.m @@ -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 diff --git a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m index 2cefe45fcb..1acd05384d 100644 --- a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m +++ b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m @@ -11,7 +11,6 @@ #import #import "KMInputMethodEventHandler.h" -#import "KMInputMethodEventHandlerProtected.h" #import "LegacyTestClient.h" #import "AppleCompliantTestClient.h" #import "TextApiCompliance.h" diff --git a/mac/Keyman4MacIM/KeymanTests/KMInputMethodBrowserClientEventHandlerTests.m b/mac/Keyman4MacIM/KeymanTests/KMInputMethodBrowserClientEventHandlerTests.m deleted file mode 100644 index cb03bb71ae..0000000000 --- a/mac/Keyman4MacIM/KeymanTests/KMInputMethodBrowserClientEventHandlerTests.m +++ /dev/null @@ -1,58 +0,0 @@ -// -// KMInputMethodBrowserClientEventHandlerTests.m -// KeymanTests -// -// Created by tom on 1/25/18. -// Copyright © 2018 SIL International. All rights reserved. -// - -#import -#import "TestAppDelegate.h" -#import "KMInputMethodBrowserClientEventHandler.h" -#import "KMInputMethodEventHandlerProtected.h" -#import - -@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 diff --git a/resources/build/test/debug-deps/child/build.sh b/resources/build/test/debug-deps/child/build.sh new file mode 100755 index 0000000000..49fe938ffa --- /dev/null +++ b/resources/build/test/debug-deps/child/build.sh @@ -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 + diff --git a/resources/build/test/debug-deps/child/dep/build.sh b/resources/build/test/debug-deps/child/dep/build.sh new file mode 100755 index 0000000000..09087786e7 --- /dev/null +++ b/resources/build/test/debug-deps/child/dep/build.sh @@ -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 diff --git a/resources/build/test/debug-deps/dep/build.sh b/resources/build/test/debug-deps/dep/build.sh new file mode 100755 index 0000000000..5fcd3d4c3d --- /dev/null +++ b/resources/build/test/debug-deps/dep/build.sh @@ -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 diff --git a/resources/build/test/debug-deps/test.sh b/resources/build/test/debug-deps/test.sh new file mode 100755 index 0000000000..d3ad4f2d63 --- /dev/null +++ b/resources/build/test/debug-deps/test.sh @@ -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 diff --git a/resources/build/test/test.sh b/resources/build/test/test.sh index 4cb78ab2c2..584be82446 100755 --- a/resources/build/test/test.sh +++ b/resources/build/test/test.sh @@ -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 diff --git a/web/src/app/webview/src/contextManager.ts b/web/src/app/webview/src/contextManager.ts index 2f6cfd6ae7..d9ef2c6e52 100644 --- a/web/src/app/webview/src/contextManager.ts +++ b/web/src/app/webview/src/contextManager.ts @@ -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) {