diff --git a/HISTORY.md b/HISTORY.md index 88ab26bf4f4..5fb16064970 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -108,6 +108,25 @@ * chore(common): move to 18.0 alpha (#10713) * chore: move to 18.0 alpha +## 17.0.317 beta 2024-05-01 + +* (#11322) +* (#11321) +* fix(linux): Fix icon for .kmp files (#11295) + +## 17.0.316 beta 2024-04-30 + +* fix(windows): check font count display none found (#11282) + +## 17.0.315 beta 2024-04-26 + +* fix(web): osk-view hidden by default on construction (#11258) +* fix(android): fixes kbd text zoom to prevent accessibility cross-effects (#11281) +* fix(developer): support export of visual keyboard when Keyman for Windows not installed (#11244) +* chore(linux): Prepare for stable release (#11301) +* fix(core): reset on frame keys (#11172) +* fix(core): ldml backspace processing should delete all markers (#11254) + ## 17.0.314 beta 2024-04-25 * fix(android/engine): URIEncode strings passed to Javascript (#11206) diff --git a/VERSION.md b/VERSION.md index 481849adf1d..582303ac684 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.26 \ No newline at end of file +18.0.26 diff --git a/common/web/gesture-recognizer/docs/web-reintegration.md b/common/web/gesture-recognizer/docs/web-reintegration.md deleted file mode 100644 index 1f5e85be4d7..00000000000 --- a/common/web/gesture-recognizer/docs/web-reintegration.md +++ /dev/null @@ -1,64 +0,0 @@ -# Web Re-integration Guide - -As this is based on refactored Keyman Engine for Web 15.0 code, there will eventually come a time to -integrate this module back into it and replace the old version. As there has been some refactoring, -this document will list notable changes to facilitate reintegration once this module is ready. - -## From feat/web/gesture-recognizer-main - -The OSK helper functions for `MouseEventEngine` and for `TouchEventEngine` have been disabled, as they -are references to components external to this module. - -## From feat/web/gesture-recognizer-base - -Starts work on a unified mouse+touch object - `GestureRecognizer` - and starts work on the unified -config object. - -Rather than specifying callback methods, we also shift to use of `EventEmitter` and an event-based model. -At this stage, the event parameters should be relatively easy to interpret and forward to the original methods. - -## From feat/web/gesture-recognizer-bounds - -This adds abstraction + generalization for gesture detection boundary logic. To translate the new config -parameters to KeymanWeb's VisualKeyboard... - -`inputStartBounds`: would be left undefined. Default behavior is a perfect match here. - -`maxRoamingBounds`: would be backed by an invisible VisualKeyboard-managed element. This element would be -and resized with the other VisualKeyboard elements. Represents the currently 1/3-a-row buffer above the -keyboard that supports continued interaction before cancelling it. - -Alternatively, this _could_ receive a custom wrapper implementation of the interface that simply appends -extra space to each side of the main target-element's `getBoundingClientRect` value. (One issue with this -approach is that the padding value may differ per device orientation.) -- This could be supported via paddedZoneSource with negative padding toward the top. - -`safeBounds`: would be left undefined. The default behavior is modeled directly after VisualKeyboard's -viewport-aware bounds detection & logic. - -`safeBoundsPadding` models the slightly-inset boundaries that are used to disable `safeBounds` near an edge -should the initial touch have started near that edge. - -## From feat/web/recognizer-multipoint-tracking - -This makes things a _bit_ more complicated than with gesture-recognizer-base. Now, the `GestureRecognizer` -object's lone event will yield an `TrackedInput` instance corresponding to the ongoing input. This object -contains a `TrackedPoint`, which tracks all data corresponding to its triggering `mousedown` or `touchstart`. -Further events for that "tracked point" are based on `TrackedPoint.path`. This approach provides the -perspective of each touch point individually for further processing... effectively splitting multi-touchpoint -events into multiple _individual_ events while keeping the metadata organized over each touchpoint's lifetime. - -## From change/web/internal-gesture-src-nomenclature - -In case someone has to trace this history on things: - -- `TrackedInput` is now `ComplexGestureSource`. -- `TrackedPoint` is now `SimpleGestureSource`. -- `TrackedPath` is now `GesturePath`. - -## From refactor/web/gesture-source-paradigm + its followup - -- `ComplexGestureSource` is now fully decommissioned; it made assumptions that were overly complicating -gesture state-machine / model-matching code. - -- Accordingly, `SimpleGestureSource` is now simply just `GestureSource`. Yay on that end! diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts b/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts index 9f909631ac8..622f04fe0cc 100644 --- a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts +++ b/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts @@ -477,6 +477,13 @@ export class GestureMatcher implements PredecessorMatch< here, as the decision is made due to a validation check against the initial item. */ this.finalize(false, 'cancelled'); + + /* + * There's no need to process the gesture-model any further... and the + * invalid state may correspond to assumptions in the path-model that + * will be invalidated if we continue. + */ + return; } } @@ -507,6 +514,7 @@ export class GestureMatcher implements PredecessorMatch< instantly fail and thus cancel. */ this.finalize(false, whileInitializing ? 'cancelled' : 'path'); + return; } // Standard path: trigger either resolution or rejection when the contact model signals either. @@ -516,6 +524,13 @@ export class GestureMatcher implements PredecessorMatch< } update() { - this.pathMatchers.forEach((matcher) => matcher.update()); + this.pathMatchers.forEach((matcher) => { + try { + matcher.update(); + } catch(err) { + console.error(err); + this.finalize(false, 'cancelled'); + } + }); } } \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/matcherSelector.ts b/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/matcherSelector.ts index c8a2cefa1a1..8e7efc73886 100644 --- a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/matcherSelector.ts +++ b/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/matcherSelector.ts @@ -409,10 +409,30 @@ export class MatcherSelector extends EventEmitter new GestureMatcher(model, unmatchedSource || priorMatcher)); + let newMatchers = gestureModelSet.map((model) => { + try { + /* + Spinning up a new gesture model means running code for that model and + path, which are defined outside of the engine. We should not allow + errors from engine-external code to prevent us from continuing with + unaffected models. + + It's also important to keep the overall flow going; this code is run + during touch-start spinup. An abrupt stop due to an unhandled error + here can lock up the AsyncDispatchQueue for touch events, locking up + the engine! + */ + return new GestureMatcher(model, unmatchedSource || priorMatcher) + } catch (err) { + console.error(err); + return null; + } + // Filter out any models that failed to 'spin-up' due to exceptions. + }).filter((entry) => !!entry); // If any newly-activating models are disqualified due to initial conditions, don't add them. newMatchers = newMatchers.filter((matcher) => !matcher.result || matcher.result.matched !== false); diff --git a/common/windows/delphi/visualkeyboard/VisualKeyboardExportHTML.pas b/common/windows/delphi/visualkeyboard/VisualKeyboardExportHTML.pas index 747dd0aca5d..614f3df8d3d 100644 --- a/common/windows/delphi/visualkeyboard/VisualKeyboardExportHTML.pas +++ b/common/windows/delphi/visualkeyboard/VisualKeyboardExportHTML.pas @@ -1,18 +1,18 @@ (* Name: VisualKeyboardExportHTML Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 20 Jun 2006 Modified Date: 8 Jun 2012 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 20 Jun 2006 - mcdurdin - Initial version 23 Aug 2006 - mcdurdin - Initial refactor for new visual keyboard 04 Dec 2006 - mcdurdin - Support new XML+XSLT OSK export @@ -50,7 +50,8 @@ implementation Controls, DebugPaths, Dialogs, - ErrorControlledRegistry, + ErrorControlledRegistry, + KeymanPaths, RegistryKeys, Unicode, utildir, @@ -63,15 +64,30 @@ implementation { TVisualKeyboardExportHTML } function GetOSKXSLPath: string; +var + keyman_root: string; begin - with TRegistryErrorControlled.Create do // I2890 - try - RootKey := HKEY_LOCAL_MACHINE; - if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_RootPath) - then Result := IncludeTrailingPathDelimiter(ReadString(SRegValue_RootPath)) + 'xml\osk\' - else Result := ExtractFilePath(ParamStr(0)) + 'xml\osk\'; - finally - Free; + Result := ExtractFilePath(ParamStr(0)) + 'xml\osk\'; + if not DirectoryExists(Result) then + begin + if TKeymanPaths.RunningFromSource(keyman_root) then + begin + Result := keyman_root + 'windows\src\engine\xml\osk\'; + end + else + begin + with TRegistryErrorControlled.Create do // I2890 + try + RootKey := HKEY_LOCAL_MACHINE; + if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_RootPath) then + Result := IncludeTrailingPathDelimiter(ReadString(SRegValue_RootPath)) + 'xml\osk\'; + finally + Free; + end; + + if not DirectoryExists(Result) then + Result := ''; + end; end; Result := GetDebugPath('Debug_OSKXSLPath', Result, True); end; @@ -131,100 +147,117 @@ procedure TVisualKeyboardExportHTML.ExportToFile(FileName: WideString); { Structure for HTML keyboard: file + subdirectory with images; always output as UTF-8? } stemp := ChangeFileExt(FileName, '') + '.xml'; - - with TVisualKeyboardExportXML.Create(FKbd) do try - ExportToFile(stemp); - finally - Free; - end; - files := TStringList.Create; - try - doc := CreateDOMDocument; + with TVisualKeyboardExportXML.Create(FKbd) do try - doc.async := False; - doc.validateOnParse := False; - doc.load(stemp); - xsldoc := ComsFreeThreadedDOMDocument.Create; + ExportToFile(stemp); + finally + Free; + end; + + files := TStringList.Create; + try + doc := CreateDOMDocument; try - xsldoc.async := False; - xsldoc.resolveExternals := True; - xsldoc.validateOnParse := False; - xsldoc.load(GetOSKXSLPath + 'osk.xsl'); + doc.async := False; + doc.validateOnParse := False; + if not doc.load(stemp) then + begin + if doc.parseError <> nil then + begin + ShowMessage('Could not load XML: '+doc.parseError.reason); + Exit; + end; + end; + xsldoc := ComsFreeThreadedDOMDocument.Create; + try + xsldoc.async := False; + xsldoc.resolveExternals := True; + xsldoc.validateOnParse := False; + if not xsldoc.load(GetOSKXSLPath + 'osk.xsl') then + begin + if xsldoc.parseError <> nil then + begin + ShowMessage('Could not load transform '+GetOSKXSLPath + 'osk.xsl: '+xsldoc.parseError.reason); + Exit; + end; + end; - xsldoc.setProperty('SelectionNamespaces', 'xmlns:oskexportdetails=''http://www.tavultesoft.com/xml/oskexportdetails'''); - xsldoc.setProperty('SelectionLanguage', 'XPath'); + xsldoc.setProperty('SelectionNamespaces', 'xmlns:oskexportdetails=''http://www.tavultesoft.com/xml/oskexportdetails'''); + xsldoc.setProperty('SelectionLanguage', 'XPath'); - nodes := xsldoc.documentElement.selectNodes('//oskexportdetails:includefile'); - for i := 0 to nodes.length - 1 do - files.Add(nodes.item[i].text); + nodes := xsldoc.documentElement.selectNodes('//oskexportdetails:includefile'); + for i := 0 to nodes.length - 1 do + files.Add(nodes.item[i].text); - xslt := ComsXSLTemplate.Create; - xslt.stylesheet := xsldoc; + xslt := ComsXSLTemplate.Create; + xslt.stylesheet := xsldoc; - xslproc := xslt.createProcessor; + xslproc := xslt.createProcessor; - xslproc.input := doc; - xslproc.addParameter('graphical', FGraphical, ''); - xslproc.addParameter('folders', FFolders, ''); + xslproc.input := doc; + xslproc.addParameter('graphical', FGraphical, ''); + xslproc.addParameter('folders', FFolders, ''); - xslproc.transform; - FOutput := xslproc.output; // doc.transformNode(xsldoc); + xslproc.transform; + FOutput := xslproc.output; // doc.transformNode(xsldoc); + finally + xsldoc := nil; + end; finally - xsldoc := nil; + doc := nil; end; - finally - doc := nil; - end; - with TStringList.Create do - try - Text := FOutput; - SaveToFile(FileName, TEncoding.UTF8); // I3337 + with TStringList.Create do + try + Text := FOutput; + SaveToFile(FileName, TEncoding.UTF8); // I3337 + finally + Free; + end; + + if FFolders then + begin + FSubdir := ChangeFileExt(FileName, '')+'_files'; + + if DirectoryExists(FSubdir) and not DirectoryEmpty(FSubdir) then + begin + if MessageDlg('The subdirectory "'+FSubdir+'" already exists. Images for the HTML file will be placed in this '+ + 'subdirectory. If you continue, any files currently in the directory will be deleted.'#13#10#13#10+ + 'Continue exporting and delete all existing files in the subdirectory?', mtConfirmation, mbOkCancel, 0) = mrCancel then Exit; + if not EmptyDirectory(FSubdir) then + if MessageDlg('The subdirectory "'+FSubdir+'" was not able to be emptied. Continue exporting anyway?', + mtConfirmation, mbOkCancel, 0) = mrCancel then Exit; + end; + + CreateDir(FSubdir); + end + else + FSubdir := ExtractFileDir(FileName); + + s := GetOSKXSLPath; + if FGraphical then + for i := 0 to files.Count - 1 do + CopyFile(PChar(s+files[i]), PChar(FSubDir+'\'+files[i]), True); finally - Free; + files.Free; end; - if FFolders then - begin - FSubdir := ChangeFileExt(FileName, '')+'_files'; - - if DirectoryExists(FSubdir) and not DirectoryEmpty(FSubdir) then + s := ChangeFileExt(stemp, '')+'_xml_files\'; + if FGraphical then + if FindFirst(s + '*', 0, f) = 0 then begin - if MessageDlg('The subdirectory "'+FSubdir+'" already exists. Images for the HTML file will be placed in this '+ - 'subdirectory. If you continue, any files currently in the directory will be deleted.'#13#10#13#10+ - 'Continue exporting and delete all existing files in the subdirectory?', mtConfirmation, mbOkCancel, 0) = mrCancel then Exit; - if not EmptyDirectory(FSubdir) then - if MessageDlg('The subdirectory "'+FSubdir+'" was not able to be emptied. Continue exporting anyway?', - mtConfirmation, mbOkCancel, 0) = mrCancel then Exit; + repeat + CopyFile(PChar(s+f.Name), PChar(FSubDir+'\'+f.Name), True); + until FindNext(f) <> 0; + FindClose(f); end; + RecursiveDelete(ExcludeTrailingPathDelimiter(s)); - CreateDir(FSubdir); - end - else - FSubdir := ExtractFileDir(FileName); - - s := GetOSKXSLPath; - if FGraphical then - for i := 0 to files.Count - 1 do - CopyFile(PChar(s+files[i]), PChar(FSubDir+'\'+files[i]), True); finally - files.Free; + DeleteFile(stemp); end; - - s := ChangeFileExt(stemp, '')+'_xml_files\'; - if FGraphical then - if FindFirst(s + '*', 0, f) = 0 then - begin - repeat - CopyFile(PChar(s+f.Name), PChar(FSubDir+'\'+f.Name), True); - until FindNext(f) <> 0; - FindClose(f); - end; - - DeleteFile(stemp); - RecursiveDelete(ExcludeTrailingPathDelimiter(s)); end; end. diff --git a/common/windows/delphi/visualkeyboard/VisualKeyboardExportXML.pas b/common/windows/delphi/visualkeyboard/VisualKeyboardExportXML.pas index 3ef240bc466..9ee294f6cce 100644 --- a/common/windows/delphi/visualkeyboard/VisualKeyboardExportXML.pas +++ b/common/windows/delphi/visualkeyboard/VisualKeyboardExportXML.pas @@ -1,18 +1,18 @@ (* Name: VisualKeyboardExportXML Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 4 Dec 2006 Modified Date: 26 Jun 2012 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 04 Dec 2006 - mcdurdin - Export to version 7, UTF-8, fix text encoding for key caps 22 Jan 2007 - mcdurdin - Export XML files to filename_xml_files subfolder 19 Mar 2007 - mcdurdin - I699 - Fix crash when exporting OSK to HTML/XML @@ -65,7 +65,6 @@ function TVisualKeyboardExportXML.XMLWideString(FileName: WideString): WideStrin nl: WideString = #13#10; begin s := '' + nl; - s := s + '' + nl; s := s + '' + nl; s := s + '
' + nl; s := s + ' '+SKeymanVersion70+'' + nl; diff --git a/core/src/km_core_processevent_api.cpp b/core/src/km_core_processevent_api.cpp index 7d48979cc4a..a2bca95ea8b 100644 --- a/core/src/km_core_processevent_api.cpp +++ b/core/src/km_core_processevent_api.cpp @@ -52,6 +52,30 @@ km_core_process_event(km_core_state *state, } km_core_status status = state->processor().process_event(state, vk, modifier_state, is_key_down, event_flags); + if (state_should_invalidate_context(state, vk, modifier_state, is_key_down, event_flags)) { + // clear the context and the app context + state->context().clear(); + state->app_context().clear(); + + // we are already committed. So we need to un-commit (remove the end of the vector) + if (state->actions().back().type == KM_CORE_IT_END) { + state->actions().pop_back(); + } + bool foundEmit = false; + km::core::action oldAction; + // try to put the invalidate item before the emit keystroke + if (state->actions().back().type == KM_CORE_IT_EMIT_KEYSTROKE) { + oldAction = state->actions().back(); + state->actions().pop_back(); + foundEmit = true; + } + state->actions().push_invalidate_context(); + if (foundEmit) { + state->actions().push_back(oldAction); + } + state->actions().commit(); + } + state->apply_actions_and_merge_app_context(); return status; diff --git a/core/src/km_core_state_api.cpp b/core/src/km_core_state_api.cpp index 8b8807b1d24..0f3fd7f1126 100644 --- a/core/src/km_core_state_api.cpp +++ b/core/src/km_core_state_api.cpp @@ -20,7 +20,8 @@ #include "processor.hpp" #include "state.hpp" - +#include "vkey_to_contextreset.hpp" +#include "kmx_file.h" using namespace km::core; @@ -363,4 +364,40 @@ km_core_cp * km_core_state_context_debug( result[s.size()] = 0; return result; -} \ No newline at end of file +} + +static bool +state_has_action_type(km_core_state *state, uint8_t type) { + return std::any_of( + state->actions().begin(), state->actions().end(), + [type](const km::core::action &a) { return a.type == type; }); +} + +bool +state_should_invalidate_context(km_core_state *state, + km_core_virtual_key vk, + uint16_t modifier_state, + uint8_t is_key_down, + uint16_t _kmn_unused(event_flags)) { + if (!is_key_down) { + return false; // don't invalidate on keyup + } + // if emit_keystroke is present, check if a context reset is needed + if (state_has_action_type(state, KM_CORE_IT_EMIT_KEYSTROKE)) { + if ( + // when a backspace keystroke is emitted, it is because we are at the start of + // context, and we want to give the application the chance to process it, e.g. + // by moving to previous field. Note that context manipulation does not result + // in an emit_keystroke backspace action, as this is handled through the + // `code_points_to_delete` field. So we always invalidate context when a + // processor emits a backspace. + vk == KM_CORE_VKEY_BKSP || + // certain modifiers invalidate context + modifier_should_contextreset(modifier_state) || + // most frame keys invalidate context + vkey_should_contextreset(vk)) { + return true; + } + } + return false; +} diff --git a/core/src/kmx/kmx_consts.cpp b/core/src/kmx/kmx_consts.cpp index ae9e7806896..009ef1a4433 100644 --- a/core/src/kmx/kmx_consts.cpp +++ b/core/src/kmx/kmx_consts.cpp @@ -104,278 +104,6 @@ const struct char_to_vkey s_char_to_vkey[] = { {0, 0, 0} }; -const bool vkey_to_contextreset[256] = { - true, //L"K_?00", // &H0 - true, //L"K_LBUTTON", // &H1 - true, //L"K_RBUTTON", // &H2 - true, //L"K_CANCEL", // &H3 - true, //L"K_MBUTTON", // &H4 - true, //L"K_?05", // &H5 - true, //L"K_?06", // &H6 - true, //L"K_?07", // &H7 - true, //L"K_BKSP", // &H8 - true, //L"K_TAB", // &H9 - true, //L"K_?0A", // &HA - true, //L"K_?0B", // &HB - true, //L"K_KP5", // &HC - true, //L"K_ENTER", // &HD - true, //L"K_?0E", // &HE - true, //L"K_?0F", // &HF - false, //L"K_SHIFT", // &H10 - false, //L"K_CONTROL", // &H11 - false, //L"K_ALT", // &H12 - true, //L"K_PAUSE", // &H13 - false, //L"K_CAPS", // &H14 - true, //L"K_KANJI?15", // &H15 - true, //L"K_KANJI?16", // &H16 - true, //L"K_KANJI?17", // &H17 - true, //L"K_KANJI?18", // &H18 - true, //L"K_KANJI?19", // &H19 - true, //L"K_?1A", // &H1A - true, //L"K_ESC", // &H1B - true, //L"K_KANJI?1C", // &H1C - true, //L"K_KANJI?1D", // &H1D - true, //L"K_KANJI?1E", // &H1E - true, //L"K_KANJI?1F", // &H1F - false, //L"K_SPACE", // &H20 - true, //L"K_PGUP", // &H21 - true, //L"K_PGDN", // &H22 - true, //L"K_END", // &H23 - true, //L"K_HOME", // &H24 - true, //L"K_LEFT", // &H25 - true, //L"K_UP", // &H26 - true, //L"K_RIGHT", // &H27 - true, //L"K_DOWN", // &H28 - true, //L"K_SEL", // &H29 - true, //L"K_PRINT", // &H2A - true, //L"K_EXEC", // &H2B - true, //L"K_PRTSCN", // &H2C - false, //L"K_INS", // &H2D - true, //L"K_DEL", // &H2E - true, //L"K_HELP", // &H2F - false, //L"K_0", // &H30 - false, //L"K_1", // &H31 - false, //L"K_2", // &H32 - false, //L"K_3", // &H33 - false, //L"K_4", // &H34 - false, //L"K_5", // &H35 - false, //L"K_6", // &H36 - false, //L"K_7", // &H37 - false, //L"K_8", // &H38 - false, //L"K_9", // &H39 - false, //L"K_?3A", // &H3A - false, //L"K_?3B", // &H3B - false, //L"K_?3C", // &H3C - false, //L"K_?3D", // &H3D - false, //L"K_?3E", // &H3E - false, //L"K_?3F", // &H3F - false, //L"K_?40", // &H40 - - false, //L"K_A", // &H41 - false, //L"K_B", // &H42 - false, //L"K_C", // &H43 - false, //L"K_D", // &H44 - false, //L"K_E", // &H45 - false, //L"K_F", // &H46 - false, //L"K_G", // &H47 - false, //L"K_H", // &H48 - false, //L"K_I", // &H49 - false, //L"K_J", // &H4A - false, //L"K_K", // &H4B - false, //L"K_L", // &H4C - false, //L"K_M", // &H4D - false, //L"K_N", // &H4E - false, //L"K_O", // &H4F - false, //L"K_P", // &H50 - false, //L"K_Q", // &H51 - false, //L"K_R", // &H52 - false, //L"K_S", // &H53 - false, //L"K_T", // &H54 - false, //L"K_U", // &H55 - false, //L"K_V", // &H56 - false, //L"K_W", // &H57 - false, //L"K_X", // &H58 - false, //L"K_Y", // &H59 - false, //L"K_Z", // &H5A - false, //L"K_?5B", // &H5B - false, //L"K_?5C", // &H5C - false, //L"K_?5D", // &H5D - false, //L"K_?5E", // &H5E - false, //L"K_?5F", // &H5F - false, //L"K_NP0", // &H60 - false, //L"K_NP1", // &H61 - false, //L"K_NP2", // &H62 - false, //L"K_NP3", // &H63 - false, //L"K_NP4", // &H64 - false, //L"K_NP5", // &H65 - false, //L"K_NP6", // &H66 - false, //L"K_NP7", // &H67 - false, //L"K_NP8", // &H68 - false, //L"K_NP9", // &H69 - false, //L"K_NPSTAR", // &H6A - false, //L"K_NPPLUS", // &H6B - false, //L"K_SEPARATOR", // &H6C - false, //L"K_NPMINUS", // &H6D - false, //L"K_NPDOT", // &H6E - false, //L"K_NPSLASH", // &H6F - true, //L"K_F1", // &H70 - true, //L"K_F2", // &H71 - true, //L"K_F3", // &H72 - true, //L"K_F4", // &H73 - true, //L"K_F5", // &H74 - true, //L"K_F6", // &H75 - true, //L"K_F7", // &H76 - true, //L"K_F8", // &H77 - true, //L"K_F9", // &H78 - true, //L"K_F10", // &H79 - true, //L"K_F11", // &H7A - true, //L"K_F12", // &H7B - true, //L"K_F13", // &H7C - true, //L"K_F14", // &H7D - true, //L"K_F15", // &H7E - true, //L"K_F16", // &H7F - true, //L"K_F17", // &H80 - true, //L"K_F18", // &H81 - true, //L"K_F19", // &H82 - true, //L"K_F20", // &H83 - true, //L"K_F21", // &H84 - true, //L"K_F22", // &H85 - true, //L"K_F23", // &H86 - true, //L"K_F24", // &H87 - - false, //L"K_?88", // &H88 - false, //L"K_?89", // &H89 - false, //L"K_?8A", // &H8A - false, //L"K_?8B", // &H8B - false, //L"K_?8C", // &H8C - false, //L"K_?8D", // &H8D - false, //L"K_?8E", // &H8E - false, //L"K_?8F", // &H8F - - false, //L"K_NUMLOCK", // &H90 - false, //L"K_SCROLL", // &H91 - - false, //L"K_?92", // &H92 - false, //L"K_?93", // &H93 - false, //L"K_?94", // &H94 - false, //L"K_?95", // &H95 - false, //L"K_?96", // &H96 - false, //L"K_?97", // &H97 - false, //L"K_?98", // &H98 - false, //L"K_?99", // &H99 - false, //L"K_?9A", // &H9A - false, //L"K_?9B", // &H9B - false, //L"K_?9C", // &H9C - false, //L"K_?9D", // &H9D - false, //L"K_?9E", // &H9E - false, //L"K_?9F", // &H9F - false, //L"K_?A0", // &HA0 - false, //L"K_?A1", // &HA1 - false, //L"K_?A2", // &HA2 - false, //L"K_?A3", // &HA3 - false, //L"K_?A4", // &HA4 - false, //L"K_?A5", // &HA5 - false, //L"K_?A6", // &HA6 - false, //L"K_?A7", // &HA7 - false, //L"K_?A8", // &HA8 - false, //L"K_?A9", // &HA9 - false, //L"K_?AA", // &HAA - false, //L"K_?AB", // &HAB - false, //L"K_?AC", // &HAC - false, //L"K_?AD", // &HAD - false, //L"K_?AE", // &HAE - false, //L"K_?AF", // &HAF - false, //L"K_?B0", // &HB0 - false, //L"K_?B1", // &HB1 - false, //L"K_?B2", // &HB2 - false, //L"K_?B3", // &HB3 - false, //L"K_?B4", // &HB4 - false, //L"K_?B5", // &HB5 - false, //L"K_?B6", // &HB6 - false, //L"K_?B7", // &HB7 - false, //L"K_?B8", // &HB8 - false, //L"K_?B9", // &HB9 - - false, //L"K_COLON", // &HBA - false, //L"K_EQUAL", // &HBB - false, //L"K_COMMA", // &HBC - false, //L"K_HYPHEN", // &HBD - false, //L"K_PERIOD", // &HBE - false, //L"K_SLASH", // &HBF - false, //L"K_BKQUOTE", // &HC0 - - false, //L"K_?C1", // &HC1 - false, //L"K_?C2", // &HC2 - false, //L"K_?C3", // &HC3 - false, //L"K_?C4", // &HC4 - false, //L"K_?C5", // &HC5 - false, //L"K_?C6", // &HC6 - false, //L"K_?C7", // &HC7 - false, //L"K_?C8", // &HC8 - false, //L"K_?C9", // &HC9 - false, //L"K_?CA", // &HCA - false, //L"K_?CB", // &HCB - false, //L"K_?CC", // &HCC - false, //L"K_?CD", // &HCD - false, //L"K_?CE", // &HCE - false, //L"K_?CF", // &HCF - false, //L"K_?D0", // &HD0 - false, //L"K_?D1", // &HD1 - false, //L"K_?D2", // &HD2 - false, //L"K_?D3", // &HD3 - false, //L"K_?D4", // &HD4 - false, //L"K_?D5", // &HD5 - false, //L"K_?D6", // &HD6 - false, //L"K_?D7", // &HD7 - false, //L"K_?D8", // &HD8 - false, //L"K_?D9", // &HD9 - false, //L"K_?DA", // &HDA - - false, //L"K_LBRKT", // &HDB - false, //L"K_BKSLASH", // &HDC - false, //L"K_RBRKT", // &HDD - false, //L"K_QUOTE", // &HDE - false, //L"K_oDF", // &HDF - false, //L"K_oE0", // &HE0 - false, //L"K_oE1", // &HE1 - false, //L"K_oE2", // &HE2 - false, //L"K_oE3", // &HE3 - false, //L"K_oE4", // &HE4 - - false, //L"K_?E5", // &HE5 - - false, //L"K_oE6", // &HE6 - - false, //L"K_?E7", // &HE7 - false, //L"K_?E8", // &HE8 - - false, //L"K_oE9", // &HE9 - false, //L"K_oEA", // &HEA - false, //L"K_oEB", // &HEB - false, //L"K_oEC", // &HEC - false, //L"K_oED", // &HED - false, //L"K_oEE", // &HEE - false, //L"K_oEF", // &HEF - false, //L"K_oF0", // &HF0 - false, //L"K_oF1", // &HF1 - false, //L"K_oF2", // &HF2 - false, //L"K_oF3", // &HF3 - false, //L"K_oF4", // &HF4 - false, //L"K_oF5", // &HF5 - - false, //L"K_?F6", // &HF6 - false, //L"K_?F7", // &HF7 - false, //L"K_?F8", // &HF8 - false, //L"K_?F9", // &HF9 - false, //L"K_?FA", // &HFA - false, //L"K_?FB", // &HFB - false, //L"K_?FC", // &HFC - false, //L"K_?FD", // &HFD - false, //L"K_?FE", // &HFE - false, //L"K_?FF" // &HFF -}; - - } // namespace kmx } // namespace core } // namespace km diff --git a/core/src/kmx/kmx_processevent.cpp b/core/src/kmx/kmx_processevent.cpp index 4b98312c369..604c6f6fe2e 100644 --- a/core/src/kmx/kmx_processevent.cpp +++ b/core/src/kmx/kmx_processevent.cpp @@ -276,7 +276,6 @@ KMX_BOOL KMX_ProcessEvent::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) // If there is now no character in the context, we want to // emit the backspace for application to use if(!pdeletecontext || *pdeletecontext == 0) { // I4933 - m_actions.QueueAction(QIT_INVALIDATECONTEXT, 0); if(m_debug_items) { m_debug_items->push_group_exit(m_actions.Length(), KM_CORE_DEBUG_FLAG_NOMATCH, gp); } @@ -301,7 +300,6 @@ KMX_BOOL KMX_ProcessEvent::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) return FALSE; } else { // I4024 // I4128 // I4287 // I4290 DebugLog(" ... IsLegacy = FALSE; IsTIP = TRUE"); // I4128 - m_actions.QueueAction(QIT_INVALIDATECONTEXT, 0); if(m_debug_items) { m_debug_items->push_group_exit(m_actions.Length(), KM_CORE_DEBUG_FLAG_NOMATCH, gp); } diff --git a/core/src/kmx/kmx_processevent.h b/core/src/kmx/kmx_processevent.h index 681c1782acc..50e84c996d8 100644 --- a/core/src/kmx/kmx_processevent.h +++ b/core/src/kmx/kmx_processevent.h @@ -126,9 +126,6 @@ struct char_to_vkey { extern const struct char_to_vkey s_char_to_vkey[]; -/** for vkeys 0..FF, 'true' if a context reset should be performed before emit */ -extern const bool vkey_to_contextreset[]; - } // namespace kmx } // namespace core } // namespace km diff --git a/core/src/ldml/ldml_processor.cpp b/core/src/ldml/ldml_processor.cpp index 9667a2275af..f90fcfaa946 100644 --- a/core/src/ldml/ldml_processor.cpp +++ b/core/src/ldml/ldml_processor.cpp @@ -227,7 +227,7 @@ ldml_processor::process_key_up(ldml_event_state &ldml_state) void ldml_processor::process_backspace(ldml_event_state &ldml_state) const { if (!!bksp_transforms) { - // process with an empty string voa the bksp transforms + // process with an empty string via the bksp transforms auto matchedContext = process_output(ldml_state, std::u32string(), bksp_transforms.get()); if (matchedContext > 0) { @@ -245,17 +245,16 @@ void ldml_event_state::emit_backspace() { // attempt to get the last char // TODO-LDML: emoji backspace auto end = state->context().rbegin(); - if (end != state->context().rend()) { - if ((*end).type == KM_CORE_CT_CHAR) { + while (end != state->context().rend()) { + if (end->type == KM_CORE_CT_CHAR) { actions.code_points_to_delete++; state->context().pop_back(); return; - } else if ((*end).type == KM_CORE_BT_MARKER) { - // nothing in actions - state->context().pop_back(); - // falls through - end wasn't a character } - } + // else loop again + assert(end->type != KM_CORE_CT_END); // inappropriate here. + state->context().pop_back(); + } /* We couldn't find a character at end of context (context is empty), so we'll pass the backspace keystroke on to the app to process; the diff --git a/core/src/meson.build b/core/src/meson.build index 9943c2fd070..7027136dd4c 100644 --- a/core/src/meson.build +++ b/core/src/meson.build @@ -49,6 +49,7 @@ kmx_files = files( 'keyboard.cpp', 'state.cpp', 'debuglog.cpp', + 'vkey_to_contextreset.cpp', 'km_core_action_api.cpp', 'km_core_context_api.cpp', 'km_core_keyboard_api.cpp', diff --git a/core/src/state.hpp b/core/src/state.hpp index 5d1e5355254..49eea8ffd2f 100644 --- a/core/src/state.hpp +++ b/core/src/state.hpp @@ -184,3 +184,23 @@ struct km_core_state : public km::core::state km_core_state(Args&&... args) : km::core::state(std::forward(args)...) {} }; + + +/** + * Evaluate the state and vkey used. + * Determine whether the context should be invalidated. + * @param state A pointer to the opaque state object. + * @param vk A virtual key that was processed. + * @param modifier_state The combinations of modifier keys set at the time key + * `vk` was pressed, bitmask from the + * km_core_modifier_state enum. + * @param is_key_down 1 if it was a key-down event + * @param event_flags Event level flags, see km_core_event_flags + * @return true if this is a state which should clear the context + */ +bool +state_should_invalidate_context(km_core_state *state, + km_core_virtual_key vk, + uint16_t modifier_state, + uint8_t is_key_down, + uint16_t event_flags); diff --git a/core/src/vkey_to_contextreset.cpp b/core/src/vkey_to_contextreset.cpp new file mode 100644 index 00000000000..914aa3e816b --- /dev/null +++ b/core/src/vkey_to_contextreset.cpp @@ -0,0 +1,278 @@ +#include "vkey_to_contextreset.hpp" + +namespace km { +namespace core { + +bool vkey_should_invalidate_context[vkey_should_invalidate_context_count] = { + true, //L"K_?00", // &H0 + true, //L"K_LBUTTON", // &H1 + true, //L"K_RBUTTON", // &H2 + true, //L"K_CANCEL", // &H3 + true, //L"K_MBUTTON", // &H4 + true, //L"K_?05", // &H5 + true, //L"K_?06", // &H6 + true, //L"K_?07", // &H7 + true, //L"K_BKSP", // &H8 + true, //L"K_TAB", // &H9 + true, //L"K_?0A", // &HA + true, //L"K_?0B", // &HB + true, //L"K_KP5", // &HC + true, //L"K_ENTER", // &HD + true, //L"K_?0E", // &HE + true, //L"K_?0F", // &HF + false, //L"K_SHIFT", // &H10 + false, //L"K_CONTROL", // &H11 + false, //L"K_ALT", // &H12 + true, //L"K_PAUSE", // &H13 + false, //L"K_CAPS", // &H14 + true, //L"K_KANJI?15", // &H15 + true, //L"K_KANJI?16", // &H16 + true, //L"K_KANJI?17", // &H17 + true, //L"K_KANJI?18", // &H18 + true, //L"K_KANJI?19", // &H19 + true, //L"K_?1A", // &H1A + true, //L"K_ESC", // &H1B + true, //L"K_KANJI?1C", // &H1C + true, //L"K_KANJI?1D", // &H1D + true, //L"K_KANJI?1E", // &H1E + true, //L"K_KANJI?1F", // &H1F + false, //L"K_SPACE", // &H20 + true, //L"K_PGUP", // &H21 + true, //L"K_PGDN", // &H22 + true, //L"K_END", // &H23 + true, //L"K_HOME", // &H24 + true, //L"K_LEFT", // &H25 + true, //L"K_UP", // &H26 + true, //L"K_RIGHT", // &H27 + true, //L"K_DOWN", // &H28 + true, //L"K_SEL", // &H29 + true, //L"K_PRINT", // &H2A + true, //L"K_EXEC", // &H2B + true, //L"K_PRTSCN", // &H2C + false, //L"K_INS", // &H2D + true, //L"K_DEL", // &H2E + true, //L"K_HELP", // &H2F + false, //L"K_0", // &H30 + false, //L"K_1", // &H31 + false, //L"K_2", // &H32 + false, //L"K_3", // &H33 + false, //L"K_4", // &H34 + false, //L"K_5", // &H35 + false, //L"K_6", // &H36 + false, //L"K_7", // &H37 + false, //L"K_8", // &H38 + false, //L"K_9", // &H39 + false, //L"K_?3A", // &H3A + false, //L"K_?3B", // &H3B + false, //L"K_?3C", // &H3C + false, //L"K_?3D", // &H3D + false, //L"K_?3E", // &H3E + false, //L"K_?3F", // &H3F + false, //L"K_?40", // &H40 + + false, //L"K_A", // &H41 + false, //L"K_B", // &H42 + false, //L"K_C", // &H43 + false, //L"K_D", // &H44 + false, //L"K_E", // &H45 + false, //L"K_F", // &H46 + false, //L"K_G", // &H47 + false, //L"K_H", // &H48 + false, //L"K_I", // &H49 + false, //L"K_J", // &H4A + false, //L"K_K", // &H4B + false, //L"K_L", // &H4C + false, //L"K_M", // &H4D + false, //L"K_N", // &H4E + false, //L"K_O", // &H4F + false, //L"K_P", // &H50 + false, //L"K_Q", // &H51 + false, //L"K_R", // &H52 + false, //L"K_S", // &H53 + false, //L"K_T", // &H54 + false, //L"K_U", // &H55 + false, //L"K_V", // &H56 + false, //L"K_W", // &H57 + false, //L"K_X", // &H58 + false, //L"K_Y", // &H59 + false, //L"K_Z", // &H5A + false, //L"K_?5B", // &H5B + false, //L"K_?5C", // &H5C + false, //L"K_?5D", // &H5D + false, //L"K_?5E", // &H5E + false, //L"K_?5F", // &H5F + false, //L"K_NP0", // &H60 + false, //L"K_NP1", // &H61 + false, //L"K_NP2", // &H62 + false, //L"K_NP3", // &H63 + false, //L"K_NP4", // &H64 + false, //L"K_NP5", // &H65 + false, //L"K_NP6", // &H66 + false, //L"K_NP7", // &H67 + false, //L"K_NP8", // &H68 + false, //L"K_NP9", // &H69 + false, //L"K_NPSTAR", // &H6A + false, //L"K_NPPLUS", // &H6B + false, //L"K_SEPARATOR", // &H6C + false, //L"K_NPMINUS", // &H6D + false, //L"K_NPDOT", // &H6E + false, //L"K_NPSLASH", // &H6F + true, //L"K_F1", // &H70 + true, //L"K_F2", // &H71 + true, //L"K_F3", // &H72 + true, //L"K_F4", // &H73 + true, //L"K_F5", // &H74 + true, //L"K_F6", // &H75 + true, //L"K_F7", // &H76 + true, //L"K_F8", // &H77 + true, //L"K_F9", // &H78 + true, //L"K_F10", // &H79 + true, //L"K_F11", // &H7A + true, //L"K_F12", // &H7B + true, //L"K_F13", // &H7C + true, //L"K_F14", // &H7D + true, //L"K_F15", // &H7E + true, //L"K_F16", // &H7F + true, //L"K_F17", // &H80 + true, //L"K_F18", // &H81 + true, //L"K_F19", // &H82 + true, //L"K_F20", // &H83 + true, //L"K_F21", // &H84 + true, //L"K_F22", // &H85 + true, //L"K_F23", // &H86 + true, //L"K_F24", // &H87 + + false, //L"K_?88", // &H88 + false, //L"K_?89", // &H89 + false, //L"K_?8A", // &H8A + false, //L"K_?8B", // &H8B + false, //L"K_?8C", // &H8C + false, //L"K_?8D", // &H8D + false, //L"K_?8E", // &H8E + false, //L"K_?8F", // &H8F + + false, //L"K_NUMLOCK", // &H90 + false, //L"K_SCROLL", // &H91 + + false, //L"K_?92", // &H92 + false, //L"K_?93", // &H93 + false, //L"K_?94", // &H94 + false, //L"K_?95", // &H95 + false, //L"K_?96", // &H96 + false, //L"K_?97", // &H97 + false, //L"K_?98", // &H98 + false, //L"K_?99", // &H99 + false, //L"K_?9A", // &H9A + false, //L"K_?9B", // &H9B + false, //L"K_?9C", // &H9C + false, //L"K_?9D", // &H9D + false, //L"K_?9E", // &H9E + false, //L"K_?9F", // &H9F + false, //L"K_?A0", // &HA0 + false, //L"K_?A1", // &HA1 + false, //L"K_?A2", // &HA2 + false, //L"K_?A3", // &HA3 + false, //L"K_?A4", // &HA4 + false, //L"K_?A5", // &HA5 + false, //L"K_?A6", // &HA6 + false, //L"K_?A7", // &HA7 + false, //L"K_?A8", // &HA8 + false, //L"K_?A9", // &HA9 + false, //L"K_?AA", // &HAA + false, //L"K_?AB", // &HAB + false, //L"K_?AC", // &HAC + false, //L"K_?AD", // &HAD + false, //L"K_?AE", // &HAE + false, //L"K_?AF", // &HAF + false, //L"K_?B0", // &HB0 + false, //L"K_?B1", // &HB1 + false, //L"K_?B2", // &HB2 + false, //L"K_?B3", // &HB3 + false, //L"K_?B4", // &HB4 + false, //L"K_?B5", // &HB5 + false, //L"K_?B6", // &HB6 + false, //L"K_?B7", // &HB7 + false, //L"K_?B8", // &HB8 + false, //L"K_?B9", // &HB9 + + false, //L"K_COLON", // &HBA + false, //L"K_EQUAL", // &HBB + false, //L"K_COMMA", // &HBC + false, //L"K_HYPHEN", // &HBD + false, //L"K_PERIOD", // &HBE + false, //L"K_SLASH", // &HBF + false, //L"K_BKQUOTE", // &HC0 + + false, //L"K_?C1", // &HC1 + false, //L"K_?C2", // &HC2 + false, //L"K_?C3", // &HC3 + false, //L"K_?C4", // &HC4 + false, //L"K_?C5", // &HC5 + false, //L"K_?C6", // &HC6 + false, //L"K_?C7", // &HC7 + false, //L"K_?C8", // &HC8 + false, //L"K_?C9", // &HC9 + false, //L"K_?CA", // &HCA + false, //L"K_?CB", // &HCB + false, //L"K_?CC", // &HCC + false, //L"K_?CD", // &HCD + false, //L"K_?CE", // &HCE + false, //L"K_?CF", // &HCF + false, //L"K_?D0", // &HD0 + false, //L"K_?D1", // &HD1 + false, //L"K_?D2", // &HD2 + false, //L"K_?D3", // &HD3 + false, //L"K_?D4", // &HD4 + false, //L"K_?D5", // &HD5 + false, //L"K_?D6", // &HD6 + false, //L"K_?D7", // &HD7 + false, //L"K_?D8", // &HD8 + false, //L"K_?D9", // &HD9 + false, //L"K_?DA", // &HDA + + false, //L"K_LBRKT", // &HDB + false, //L"K_BKSLASH", // &HDC + false, //L"K_RBRKT", // &HDD + false, //L"K_QUOTE", // &HDE + false, //L"K_oDF", // &HDF + false, //L"K_oE0", // &HE0 + false, //L"K_oE1", // &HE1 + false, //L"K_oE2", // &HE2 + false, //L"K_oE3", // &HE3 + false, //L"K_oE4", // &HE4 + + false, //L"K_?E5", // &HE5 + + false, //L"K_oE6", // &HE6 + + false, //L"K_?E7", // &HE7 + false, //L"K_?E8", // &HE8 + + false, //L"K_oE9", // &HE9 + false, //L"K_oEA", // &HEA + false, //L"K_oEB", // &HEB + false, //L"K_oEC", // &HEC + false, //L"K_oED", // &HED + false, //L"K_oEE", // &HEE + false, //L"K_oEF", // &HEF + false, //L"K_oF0", // &HF0 + false, //L"K_oF1", // &HF1 + false, //L"K_oF2", // &HF2 + false, //L"K_oF3", // &HF3 + false, //L"K_oF4", // &HF4 + false, //L"K_oF5", // &HF5 + + false, //L"K_?F6", // &HF6 + false, //L"K_?F7", // &HF7 + false, //L"K_?F8", // &HF8 + false, //L"K_?F9", // &HF9 + false, //L"K_?FA", // &HFA + false, //L"K_?FB", // &HFB + false, //L"K_?FC", // &HFC + false, //L"K_?FD", // &HFD + false, //L"K_?FE", // &HFE + false, //L"K_?FF" // &HFF +}; + +} +} diff --git a/core/src/vkey_to_contextreset.hpp b/core/src/vkey_to_contextreset.hpp new file mode 100644 index 00000000000..c7c6b1953d0 --- /dev/null +++ b/core/src/vkey_to_contextreset.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include "kmx_file.h" + +namespace km { +namespace core { + +static const auto vkey_should_invalidate_context_count = 256; +/** true for any vkeys which require a context reset (such as frame keys) */ +extern bool vkey_should_invalidate_context[vkey_should_invalidate_context_count]; + +/** @return true for any vkeys which require a context reset (such as frame keys) */ +inline bool vkey_should_contextreset(km_core_virtual_key vk) { + return ((vk < vkey_should_invalidate_context_count) && vkey_should_invalidate_context[vk]); +} + +/** @return true for modifier states that are involved with context reset */ +inline bool modifier_should_contextreset(uint16_t modifier_state) { + // ctrl or alt + return (modifier_state & (K_CTRLFLAG | K_ALTFLAG | LCTRLFLAG | RCTRLFLAG | LALTFLAG | RALTFLAG)); +} + + +} +} diff --git a/core/tests/unit/kmnkbd/debug_api.cpp b/core/tests/unit/kmnkbd/debug_api.cpp index 50d6ba72ba0..6542f3c378f 100644 --- a/core/tests/unit/kmnkbd/debug_api.cpp +++ b/core/tests/unit/kmnkbd/debug_api.cpp @@ -120,8 +120,8 @@ void test_debugging_function_key() { assert(debug_items(test_state, { km_core_state_debug_item{KM_CORE_DEBUG_BEGIN, KM_CORE_DEBUG_FLAG_UNICODE, {KM_CORE_VKEY_F1, 0, 0}}, km_core_state_debug_item{KM_CORE_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, - km_core_state_debug_item{KM_CORE_DEBUG_GROUP_EXIT, KM_CORE_DEBUG_FLAG_NOMATCH, {}, {u"", &gp, nullptr, {}, 1}}, - km_core_state_debug_item{KM_CORE_DEBUG_END, KM_CORE_DEBUG_FLAG_OUTPUTKEYSTROKE, {}, {u"", nullptr, nullptr, {}, 1}} + km_core_state_debug_item{KM_CORE_DEBUG_GROUP_EXIT, KM_CORE_DEBUG_FLAG_NOMATCH, {}, {u"", &gp, nullptr, {}, 0}}, + km_core_state_debug_item{KM_CORE_DEBUG_END, KM_CORE_DEBUG_FLAG_OUTPUTKEYSTROKE, {}, {u"", nullptr, nullptr, {}, 0}} })); assert(action_items(test_state, { @@ -439,8 +439,8 @@ void test_backspace_markers() { assert(debug_items(test_state, { km_core_state_debug_item{KM_CORE_DEBUG_BEGIN, KM_CORE_DEBUG_FLAG_UNICODE, {KM_CORE_VKEY_BKSP, 0, 0}}, km_core_state_debug_item{KM_CORE_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, - km_core_state_debug_item{KM_CORE_DEBUG_GROUP_EXIT, 2, {}, {u"", &gp, nullptr, {}, 3}}, - km_core_state_debug_item{KM_CORE_DEBUG_END, 1, {}, {u"", nullptr, nullptr, {}, 3}}, + km_core_state_debug_item{KM_CORE_DEBUG_GROUP_EXIT, 2, {}, {u"", &gp, nullptr, {}, 2}}, + km_core_state_debug_item{KM_CORE_DEBUG_END, 1, {}, {u"", nullptr, nullptr, {}, 2}}, })); km_core_action_item bksp = {KM_CORE_IT_BACK}; diff --git a/core/tests/unit/ldml/invalid-keyboards/ik_000_null_invalid.xml b/core/tests/unit/ldml/invalid-keyboards/ik_000_null_invalid.xml index a16eb613468..6e10b734e97 100644 --- a/core/tests/unit/ldml/invalid-keyboards/ik_000_null_invalid.xml +++ b/core/tests/unit/ldml/invalid-keyboards/ik_000_null_invalid.xml @@ -1,4 +1,4 @@ - +Note that the 'expected' lines are cumulative unless there's a reset event. + +@@keys: [K_BKQUOTE][K_1][K_ENTER][K_Z] +@@expected: z +Comment: 1=gap, enter=not mapped/mappable - reset. (z used as a subtest separator) + +@@keys: [K_Q][K_BKQUOTE][K_Z] +@@expected: zAz +Comment: q\m{q}a => A due to transform + +@@keys: [K_Q][K_2][K_BKQUOTE][K_Z] +@@expected: zAzAz +Comment: 2=not mapped, but NO reset. + +@@keys: [K_Q][K_1][K_BKQUOTE][K_Z] +@@expected: zAzAzAz +Comment: 1 is a gap (no effect) so no ctx reset + +@@keys: [K_Q][K_ENTER][K_BKQUOTE][K_Z] +@@expected: az +Comment: enter=not mappable, causes ctx reset. - diff --git a/core/tests/unit/ldml/keyboards/k_210_marker-test.xml b/core/tests/unit/ldml/keyboards/k_210_marker-test.xml index 5a36d1941b3..22865b12c5c 100644 --- a/core/tests/unit/ldml/keyboards/k_210_marker-test.xml +++ b/core/tests/unit/ldml/keyboards/k_210_marker-test.xml @@ -62,4 +62,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/k_210_marker.xml b/core/tests/unit/ldml/keyboards/k_210_marker.xml index 0d25de75ac6..6f2eb9021a9 100644 --- a/core/tests/unit/ldml/keyboards/k_210_marker.xml +++ b/core/tests/unit/ldml/keyboards/k_210_marker.xml @@ -17,14 +17,15 @@ + - + - + @@ -56,5 +57,12 @@ + + + + + + + diff --git a/core/tests/unit/ldml/ldml.cpp b/core/tests/unit/ldml/ldml.cpp index bef485a8783..3c27046b232 100644 --- a/core/tests/unit/ldml/ldml.cpp +++ b/core/tests/unit/ldml/ldml.cpp @@ -39,8 +39,6 @@ namespace { bool g_beep_found = false; -bool g_already_complained = false; - km_core_option_item test_env_opts[] = { KM_CORE_OPTIONS_END @@ -115,49 +113,54 @@ apply_action( {(uint32_t)act.marker}}); break; case KM_CORE_IT_BACK: + { + // single char removed in context + km_core_usv ch = 0; + bool matched_text = false; + // assume the backspace came from set_action() and there's no further info. + assert(act.backspace.expected_type == KM_CORE_BT_CHAR); + assert(act.backspace.expected_value == 0); // It is valid for a backspace to be received with an empty text store // as the user can press backspace with no text in the store and Keyman // will pass that back to the client, as the client may do additional // processing at start of a text store, e.g. delete from a previous cell // in a table. Or, if Keyman has a cached context, then there may be // additional text in the text store that Keyman can't see. - if (act.backspace.expected_type == KM_CORE_BT_MARKER) { - assert(!context.empty()); - assert(context.back().type == KM_CORE_CT_MARKER); - context.pop_back(); - // no change to text store. - } else if (text_store.length() > 0) { + // If there's anything in the text store, pop it off. Two if a pair. + if (text_store.length() > 0) { assert(!context.empty() && !text_store.empty()); - km_core_usv ch = text_store.back(); + const auto ch1 = text_store.back(); text_store.pop_back(); - if (text_store.length() > 0 && Uni_IsSurrogate2(ch)) { - auto ch1 = text_store.back(); - if (Uni_IsSurrogate1(ch1)) { + if (text_store.length() > 0 && Uni_IsSurrogate2(ch1)) { + const auto ch2 = text_store.back(); + if (Uni_IsSurrogate1(ch2)) { // We'll only pop the next character off it is actually a // surrogate pair - ch = Uni_SurrogateToUTF32(ch1, ch); + ch = Uni_SurrogateToUTF32(ch2, ch1); // reverse order text_store.pop_back(); + } else { + ch = 0xFFFF; // unpaired } + } else { + ch = ch1; // single char } - if (act.backspace.expected_type == KM_CORE_BT_CHAR) { - if (act.backspace.expected_value == 0) { - // using set_action() doesn't provide for expected backspaces, so can't validate here - // only complain once. - if (!g_already_complained) { - std::cerr << "Note: TODO-LDML: not validating backspace.expected_value nor ch - no information available." << std::endl; - g_already_complained = true; - } - } else { - assert(ch == act.backspace.expected_value); - assert(context.back().character == ch); + } else { + matched_text = true; // no text to match as context is empty. + } + // now, we need to simulate what ldml_processor::emit_backspace() is going to do. + auto end = context.rbegin(); + while (end != context.rend()) { + if (end->type == KM_CORE_CT_CHAR) { + assert(!matched_text); + assert_equal(end->character, ch); // expect popped char to be same as what's in context + matched_text = true; + context.pop_back(); + break; // exit on first real char } - assert(context.back().type == KM_CORE_CT_CHAR); + assert(end->type != KM_CORE_CT_END); // inappropriate here. context.pop_back(); - } else { - // assume it's otherwise KM_CORE_BT_UNKNOWN - assert(act.backspace.expected_type == KM_CORE_BT_UNKNOWN); - assert(context.empty()); // if KM_CORE_BT_UNKNOWN, context should be empty. } + assert(matched_text); } break; case KM_CORE_IT_PERSIST_OPT: @@ -195,64 +198,64 @@ apply_action( /** * verify the current context -*/ + */ void -verify_context(std::u16string& text_store, km_core_state* &test_state, std::vector &test_context) { - // Compare context and text store at each step - should be identical - size_t n = 0; - km_core_context_item* citems = nullptr; - try_status(km_core_context_get(km_core_state_context(test_state), &citems)); - try_status(context_items_to_utf16(citems, nullptr, &n)); - km_core_cp *buf = new km_core_cp[n]; - try_status(context_items_to_utf16(citems, buf, &n)); - std::cout << "context (raw): "; // output including markers (which aren't in 'buf' here) - for (auto ci = citems; ci->type != KM_CORE_CT_END; ci++) { - switch(ci->type) { - case KM_CORE_CT_CHAR: - std::cout << "U+" << std::setw(4) << std::hex << ci->character << std::dec << " "; - break; - case KM_CORE_CT_MARKER: - std::cout << "\\m{" << ci->character << "} "; - break; - default: - std::cout << "type#" << ci->type << " "; - } +verify_context(std::u16string &text_store, km_core_state *&test_state, std::vector &test_context) { + // Compare context and text store at each step - should be identical + size_t n = 0; + km_core_context_item *citems = nullptr; + try_status(km_core_context_get(km_core_state_context(test_state), &citems)); + try_status(context_items_to_utf16(citems, nullptr, &n)); + km_core_cp *buf = new km_core_cp[n]; + try_status(context_items_to_utf16(citems, buf, &n)); + std::cout << "context (raw): "; // output including markers (which aren't in 'buf' here) + for (auto ci = citems; ci->type != KM_CORE_CT_END; ci++) { + switch (ci->type) { + case KM_CORE_CT_CHAR: + std::cout << "U+" << std::setw(4) << std::hex << ci->character << std::dec << " "; + break; + case KM_CORE_CT_MARKER: + std::cout << "\\m{" << ci->character << "} "; + break; + default: + std::cout << "type#" << ci->type << " "; } - std::cout << std::endl; - std::cout << "context : " << string_to_hex(buf) << " [" << buf << "]" << std::endl; - std::cout << "testcontext "; - std::cout.fill('0'); - for (auto i = test_context.begin(); i < test_context.end(); i++) { - switch(i->type) { - case KM_CORE_CT_CHAR: - std::cout << "U+" << std::setw(4) << std::hex << i->character << std::dec << " "; - break; - case KM_CORE_CT_MARKER: - std::cout << "\\m{" << i->character << "} "; - break; - default: - std::cout << "type#" << i->type << " "; - } + } + std::cout << std::endl; + std::cout << "context : " << string_to_hex(buf) << " [" << buf << "]" << std::endl; + std::cout << "testcontext "; + std::cout.fill('0'); + for (auto i = test_context.begin(); i < test_context.end(); i++) { + switch (i->type) { + case KM_CORE_CT_CHAR: + std::cout << "U+" << std::setw(4) << std::hex << i->character << std::dec << " "; + break; + case KM_CORE_CT_MARKER: + std::cout << "\\m{" << i->character << "} "; + break; + default: + std::cout << "type#" << i->type << " "; } - std::cout << std::endl; - - // Verify that both our local test_context and the core's test_state.context have - // not diverged - auto ci = citems; - for (auto test_ci = test_context.begin(); ci->type != KM_CORE_CT_END || test_ci != test_context.end(); ci++, test_ci++) { - // skip over markers, they won't be in test_context - while (ci->type == KM_CORE_CT_MARKER) { - ci++; - } - // exit if BOTH are at end. - if (ci->type == KM_CORE_CT_END && test_ci == test_context.end()) { - break; // success - } - // fail if only ONE is at end - assert(ci->type != KM_CORE_CT_END && test_ci != test_context.end()); - // fail if type and marker don't match. - assert(test_ci->type == ci->type && test_ci->marker == ci->marker); + } + std::cout << std::endl; + + // Verify that both our local test_context and the core's test_state.context have + // not diverged + auto ci = citems; + for (auto test_ci = test_context.begin();; ci++, test_ci++) { + // skip over markers, they won't be in test_context + while (ci->type == KM_CORE_CT_MARKER) { + ci++; } + // exit if BOTH are at end. + if (ci->type == KM_CORE_CT_END && test_ci == test_context.end()) { + break; // success + } + // fail if only ONE is at end + assert(ci->type != KM_CORE_CT_END && test_ci != test_context.end()); + // fail if type and marker don't match. + assert(test_ci->type == ci->type && test_ci->marker == ci->marker); + } km_core_context_items_dispose(citems); if (text_store != buf) { @@ -329,6 +332,11 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests test_state, p.vk, p.modifier_state | test_source.caps_lock_state(), key_down, KM_CORE_EVENT_FLAG_DEFAULT)); // TODO-LDML: for now. Should send touch and hardware events. + if (state_should_invalidate_context(test_state, p.vk, p.modifier_state | test_source.caps_lock_state(), key_down, + KM_CORE_EVENT_FLAG_DEFAULT)) { + test_context.clear(); + text_store.clear(); + } for (auto act = km_core_state_action_items(test_state, nullptr); act->type != KM_CORE_IT_END; act++) { apply_action(test_state, *act, text_store, test_context, test_source, test_context); } diff --git a/core/tests/unit/ldml/ldml_test_source.cpp b/core/tests/unit/ldml/ldml_test_source.cpp index 71c5a7ee4e0..2db7efe6e31 100644 --- a/core/tests/unit/ldml/ldml_test_source.cpp +++ b/core/tests/unit/ldml/ldml_test_source.cpp @@ -280,13 +280,15 @@ LdmlEmbeddedTestSource::load_source( const km::core::path &path ) { if (!line.length()) continue; if (line.compare(0, s_keys.length(), s_keys) == 0) { - keys = line.substr(s_keys.length()); - trim(keys); + auto k = line.substr(s_keys.length()); + trim(k); + keys.emplace_back(k); } else if (is_token(s_expected, line)) { if (line == "\\b") { expected_beep = true; } else { - expected = parse_source_string(line); + // allow multiple expected lines + expected.emplace_back(parse_source_string(line)); } } else if (is_token(s_expecterror, line)) { expected_error = true; @@ -297,8 +299,15 @@ LdmlEmbeddedTestSource::load_source( const km::core::path &path ) { } } - if (keys == "") { + if (keys.empty() && expected.empty() && !expected_error) { + // don't note this, the parent will complain if there's neither json nor embedded + return __LINE__; + } else if (keys.empty()) { // We must at least have a key sequence to run the test + std::cerr << "Need at least one key sequence." << std::endl; + return __LINE__; + } else if(!expected_error && (keys.size() != expected.size())) { + std::cerr << "Need the same number of " << s_keys << " and " << s_expected << " lines." << std::endl; return __LINE__; } @@ -387,15 +396,19 @@ LdmlEmbeddedTestSource::vkey_to_event(std::string const &vk_event) { void LdmlEmbeddedTestSource::next_action(ldml_action &fillin) { - if (is_done) { + if (is_done || keys.empty()) { // We were already done. return done. fillin.type = LDML_ACTION_DONE; return; - } else if(keys.empty()) { - // Got to the end of the keys. time to check + } else if(keys[0].empty()) { + // Got to the end of a key set. time to check fillin.type = LDML_ACTION_CHECK_EXPECTED; - fillin.string = expected; // copy expected - is_done = true; // so we get DONE next time + fillin.string = expected[0]; // copy expected + expected.pop_front(); + keys.pop_front(); + if (keys.empty()) { + is_done = true; // so we get DONE next time + } } else { fillin.type = LDML_ACTION_KEY_EVENT; fillin.k = next_key(); @@ -406,7 +419,7 @@ LdmlEmbeddedTestSource::next_action(ldml_action &fillin) { key_event LdmlEmbeddedTestSource::next_key() { // mutate this->keys - return next_key(keys); + return next_key(keys[0]); } key_event diff --git a/core/tests/unit/ldml/ldml_test_source.hpp b/core/tests/unit/ldml/ldml_test_source.hpp index a0eed2ec8e1..4637af9bd44 100644 --- a/core/tests/unit/ldml/ldml_test_source.hpp +++ b/core/tests/unit/ldml/ldml_test_source.hpp @@ -149,8 +149,9 @@ class LdmlEmbeddedTestSource : public LdmlTestSource { key_event next_key(std::string &keys); key_event next_key(); - std::string keys = ""; - std::u16string expected = u"", context = u""; + std::deque keys; + std::deque expected; + std::u16string context = u""; bool expected_beep = false; bool expected_error = false; bool is_done = false; diff --git a/developer/src/.gitignore b/developer/src/.gitignore index 31b79db664b..7ce2ad52302 100644 --- a/developer/src/.gitignore +++ b/developer/src/.gitignore @@ -32,6 +32,7 @@ inst/setup.zip inst/templates.wxs inst/xml.wxs inst/kmc.wxs +tike/xml/osk # /developer/TIKE/ tike/stock.kct diff --git a/developer/src/inst/Makefile b/developer/src/inst/Makefile index ae2e328c821..343fa7063e3 100644 --- a/developer/src/inst/Makefile +++ b/developer/src/inst/Makefile @@ -61,6 +61,7 @@ clean: -del /Q cef.wxs -del /Q templates.wxs -del /Q kmc.wxs + -rd /s /q $(DEVELOPER_ROOT)\src\tike\xml\osk test-releaseexists: cd $(DEVELOPER_ROOT)\src\inst diff --git a/developer/src/inst/download.in.mak b/developer/src/inst/download.in.mak index 54f6940782b..46fa6f6a16b 100644 --- a/developer/src/inst/download.in.mak +++ b/developer/src/inst/download.in.mak @@ -45,9 +45,12 @@ clean-heat: clean-heat-kmc heat-xml: # We copy the files to a temp folder in order to exclude thumbs.db, .vs, etc from harvesting +# We also copy over the OSK files from Keyman Engine (#11199) -rmdir /s/q $(KEYMAN_WIX_TEMP_XML) mkdir $(KEYMAN_WIX_TEMP_XML) + xcopy $(KEYMAN_ROOT)\windows\src\engine\xml\osk $(DEVELOPER_ROOT)\src\tike\xml\osk\ /s /i xcopy $(DEVELOPER_ROOT)\src\tike\xml\* $(KEYMAN_WIX_TEMP_XML)\ /s + -del /f /s /q $(KEYMAN_WIX_TEMP_XML)\Thumbs.db -rmdir /s/q $(KEYMAN_WIX_TEMP_XML)\app\node_modules -for /f %i in ('dir /a:d /s /b $(KEYMAN_WIX_TEMP_XML)\.vs') do rd /s /q %i diff --git a/ios/.swiftlint.yml b/ios/.swiftlint.yml index bfb08663e31..5abe0cedd10 100644 --- a/ios/.swiftlint.yml +++ b/ios/.swiftlint.yml @@ -12,3 +12,7 @@ identifier_name: min_length: warning: 0 error: 0 + +# allow underscore to avoid unexpected lint error in generated file in Xcode 15.3 +type_name: + allowed_symbols: "_" diff --git a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj index 0490f265501..9dc49f1739e 100644 --- a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj +++ b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj @@ -1354,7 +1354,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = "/usr/bin/env bash"; - shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n. \"$KEYMAN_ROOT/resources/build/xcode-utils.sh\"\n\nphaseSetBundleVersions true\n"; + shellScript = "\"$KEYMAN_ROOT/resources/build/xcode-wrap.sh\" \"$KEYMAN_ROOT/resources/build/set-bundle-versions-tagged.sh\"\n"; showEnvVarsInLog = 0; }; CEC0C66F2410B005003E1BCD /* Run Script - upload dsyms to Sentry */ = { diff --git a/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj b/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj index d1c3f929be3..fdeae450f0d 100644 --- a/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj +++ b/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj @@ -950,7 +950,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = "/usr/bin/env bash"; - shellScript = "verstr1=$(/usr/libexec/PlistBuddy -c \"Print CFBundleShortVersionString\" \"$SRCROOT/Keyman-Info.plist\")\nverstr2=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"$SRCROOT/Keyman-Info.plist\")\n/usr/libexec/PlistBuddy \"$SRCROOT/Settings.bundle/Root.plist\" -c \"set PreferenceSpecifiers:0:DefaultValue $verstr1 (build $verstr2)\"\n\nif which swiftlint >/dev/null; then\n swiftlint --config ../../.swiftlint.yml\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + shellScript = "#verstr1=$(/usr/libexec/PlistBuddy -c \"Print CFBundleShortVersionString\" \"$SRCROOT/Keyman-Info.plist\")\n#verstr2=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"$SRCROOT/Keyman-Info.plist\")\n#/usr/libexec/PlistBuddy \"$SRCROOT/Settings.bundle/Root.plist\" -c \"set PreferenceSpecifiers:0:DefaultValue $verstr1 (build $verstr2)\"\n\nif which swiftlint >/dev/null; then\n swiftlint --config ../../.swiftlint.yml\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; showEnvVarsInLog = 0; }; CE002CB32408B372002026CE /* Run Script - upload dsyms to sentry */ = { @@ -970,7 +970,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = "/usr/bin/env bash"; - shellScript = ". \"$KEYMAN_ROOT/resources/build/xcode-utils.sh\"\n\nif [ ${UPLOAD_SENTRY:-false} = true ]; then\n # Calls resource script to perform the dSYM upload\n phaseSentryDsymUpload \"keyman-ios\"\nfi\n"; + shellScript = ". \"$KEYMAN_ROOT/resources/build/xcode-utils.sh\"\n\nif [ ${UPLOAD_SENTRY:-false} = true ]; then\n # Calls resource script to perform the dSYM upload\n \"$KEYMAN_ROOT/resources/build/xcode-wrap.sh\" \"$KEYMAN_ROOT/resources/build/sentry-dsym-upload.sh\"\nfi\n"; }; CE06AA671F161422006D91C3 /* Run Script */ = { isa = PBXShellScriptBuildPhase; @@ -1002,7 +1002,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = "/usr/bin/env bash"; - shellScript = ". \"$KEYMAN_ROOT/resources/build/xcode-utils.sh\"\n\n# Calls resource script to update build products' versioning.\n# true: applies VERSION_WITH_TAG to custom KeymanVersionWithTag plist member used for in-app display\nphaseSetBundleVersions true\n\n# Also updates the version string for Settings\nsetSettingsBundleVersion\n"; + shellScript = "\"$KEYMAN_ROOT/resources/build/xcode-wrap.sh\" \"$KEYMAN_ROOT/resources/build/set-bundle-versions-and-settings-tagged.sh\"\n"; showEnvVarsInLog = 0; }; CED3CFDA240E49DF001540A1 /* ShellScript */ = { @@ -1020,7 +1020,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = "/usr/bin/env bash"; - shellScript = ". \"$KEYMAN_ROOT/resources/build/xcode-utils.sh\"\n\n# Calls resource script to update build products' versioning.\nphaseSetBundleVersions\n"; + shellScript = "\"$KEYMAN_ROOT/resources/build/xcode-wrap.sh\" \"$KEYMAN_ROOT/resources/build/set-bundle-versions-untagged.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ diff --git a/linux/debian/control b/linux/debian/control index 6eeb24450ab..611d586225c 100644 --- a/linux/debian/control +++ b/linux/debian/control @@ -23,7 +23,7 @@ Build-Depends: metacity, ninja-build, perl, - pkg-config, + pkgconf, python3-all (>= 3.5), python3-dbus, python3-gi, @@ -41,9 +41,9 @@ Build-Depends: python3-xdg, xserver-xephyr, xvfb, -Standards-Version: 4.6.2 -Vcs-Git: https://github.com/keymanapp/keyman.git -b beta [linux/debian] -Vcs-Browser: https://github.com/keymanapp/keyman/tree/beta/linux/debian +Standards-Version: 4.7.0 +Vcs-Git: https://github.com/keymanapp/keyman.git -b stable-17.0 [linux/debian] +Vcs-Browser: https://github.com/keymanapp/keyman/tree/stable-17.0/linux/debian Homepage: https://www.keyman.com Rules-Requires-Root: binary-targets diff --git a/linux/keyman-config/resources/keyman.sharedmimeinfo b/linux/keyman-config/resources/keyman.sharedmimeinfo index 569866b403b..c1bb65e33da 100644 --- a/linux/keyman-config/resources/keyman.sharedmimeinfo +++ b/linux/keyman-config/resources/keyman.sharedmimeinfo @@ -5,6 +5,7 @@ + Keyman keyboard installation link diff --git a/mac/Keyman4Mac/Keyman4Mac.xcodeproj/project.pbxproj b/mac/Keyman4Mac/Keyman4Mac.xcodeproj/project.pbxproj index ff087f05441..25cc7ca8005 100644 --- a/mac/Keyman4Mac/Keyman4Mac.xcodeproj/project.pbxproj +++ b/mac/Keyman4Mac/Keyman4Mac.xcodeproj/project.pbxproj @@ -328,7 +328,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -375,7 +375,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; }; @@ -399,7 +399,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.13; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = org.sil.Keyman4Mac; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -425,7 +425,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = org.sil.Keyman4Mac; PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_VERSION = 0.0.1; @@ -451,6 +451,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Keyman4Mac.app/Contents/MacOS/Keyman4Mac"; }; @@ -471,6 +472,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Keyman4Mac.app/Contents/MacOS/Keyman4Mac"; }; diff --git a/mac/Keyman4MacIM/Keyman-template.dmg b/mac/Keyman4MacIM/Keyman-template.dmg index 8a67ad5da50..f23b470e2cc 100644 Binary files a/mac/Keyman4MacIM/Keyman-template.dmg and b/mac/Keyman4MacIM/Keyman-template.dmg differ diff --git a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj index 902fca24609..a9fc9f5e859 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj +++ b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj @@ -1231,7 +1231,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -1279,7 +1279,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; }; @@ -1318,7 +1318,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.13; ONLY_ACTIVE_ARCH = YES; OTHER_CODE_SIGN_FLAGS = "--timestamp --verbose"; PRODUCT_BUNDLE_IDENTIFIER = "keyman.inputmethod.$(PRODUCT_NAME:rfc1034identifier)"; @@ -1362,7 +1362,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.13; OTHER_CODE_SIGN_FLAGS = "--timestamp --verbose"; PRODUCT_BUNDLE_IDENTIFIER = "keyman.inputmethod.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1406,7 +1406,7 @@ "@loader_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = org.sil.consoleTestbed.KeymanTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1440,7 +1440,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = org.sil.consoleTestbed.KeymanTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/mac/Keyman4MacIM/Podfile b/mac/Keyman4MacIM/Podfile index cd8e971f23b..5f203a69955 100644 --- a/mac/Keyman4MacIM/Podfile +++ b/mac/Keyman4MacIM/Podfile @@ -1,5 +1,5 @@ # Uncomment the next line to define a global platform for your project -platform :osx, '10.10' +platform :osx, '10.13' use_frameworks! target 'Keyman' do @@ -7,11 +7,11 @@ target 'Keyman' do # use_frameworks! # Pods for Keyman - pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '5.2.1' + pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '8.24.0' target 'KeymanTests' do inherit! :search_paths - pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '5.2.1' + pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '8.24.0' use_frameworks! # Pods for testing end diff --git a/mac/Keyman4MacIM/Podfile.lock b/mac/Keyman4MacIM/Podfile.lock index a161db72ea0..6eb733ea9ad 100644 --- a/mac/Keyman4MacIM/Podfile.lock +++ b/mac/Keyman4MacIM/Podfile.lock @@ -1,24 +1,24 @@ PODS: - - Sentry (5.2.1): - - Sentry/Core (= 5.2.1) - - Sentry/Core (5.2.1) + - Sentry (8.24.0): + - Sentry/Core (= 8.24.0) + - Sentry/Core (8.24.0) DEPENDENCIES: - - Sentry (from `https://github.com/getsentry/sentry-cocoa.git`, tag `5.2.1`) + - Sentry (from `https://github.com/getsentry/sentry-cocoa.git`, tag `8.24.0`) EXTERNAL SOURCES: Sentry: :git: https://github.com/getsentry/sentry-cocoa.git - :tag: 5.2.1 + :tag: 8.24.0 CHECKOUT OPTIONS: Sentry: :git: https://github.com/getsentry/sentry-cocoa.git - :tag: 5.2.1 + :tag: 8.24.0 SPEC CHECKSUMS: - Sentry: 7d075cae43a9a518fdd51e258b6212f0527c51cd + Sentry: 2f6baed15a3f8056b875fc903dc3dcb2903117f4 -PODFILE CHECKSUM: 521a56f741ba869ecf0fd64a7773173265973269 +PODFILE CHECKSUM: 483593d0b294fc5751c2490061e01211db3f9ecc -COCOAPODS: 1.11.2 +COCOAPODS: 1.15.2 diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac.xcodeproj/project.pbxproj b/mac/KeymanEngine4Mac/KeymanEngine4Mac.xcodeproj/project.pbxproj index 0a46301b7f5..d5c330e1f4d 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac.xcodeproj/project.pbxproj +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac.xcodeproj/project.pbxproj @@ -594,7 +594,7 @@ ); INSTALL_PATH = ""; "LIBRARY_SEARCH_PATHS[arch=*]" = "$(PROJECT_DIR)/../../core/build/mac/debug"; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -647,7 +647,7 @@ ); INSTALL_PATH = ""; "LIBRARY_SEARCH_PATHS[arch=*]" = "$(PROJECT_DIR)/../../core/build/mac/release"; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; VERSIONING_SYSTEM = "apple-generic"; @@ -685,7 +685,7 @@ "$(PROJECT_DIR)/../../core/build/mac-x86_64/debug/subprojects/icu/source/i18n", "$(PROJECT_DIR)/../../core/build/mac/debug", ); - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.13; ONLY_ACTIVE_ARCH = NO; "OTHER_LDFLAGS[arch=*]" = "-lstdc++"; PRODUCT_BUNDLE_IDENTIFIER = org.sil.KeymanEngine4Mac; @@ -728,7 +728,7 @@ "$(PROJECT_DIR)/../../core/build/mac-x86_64/release/subprojects/icu/source/i18n", "$(PROJECT_DIR)/../../core/build/mac/release", ); - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.13; "OTHER_LDFLAGS[arch=*]" = "-lstdc++"; PRODUCT_BUNDLE_IDENTIFIER = org.sil.KeymanEngine4Mac; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -753,6 +753,7 @@ ); INFOPLIST_FILE = KeymanEngine4MacTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = "Tavultesoft.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; @@ -768,6 +769,7 @@ ); INFOPLIST_FILE = KeymanEngine4MacTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = "Tavultesoft.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; diff --git a/mac/help/about/requirements.md b/mac/help/about/requirements.md index 1775c0dd8c2..d92272a7304 100644 --- a/mac/help/about/requirements.md +++ b/mac/help/about/requirements.md @@ -6,19 +6,17 @@ title: System Requirements Keyman supports the following Mac operating systems: -* Mac OS X Yosemite (10.10) -* Mac OS X El Capitan (10.11) -* macOS Sierra (10.12) * macOS High Sierra (10.13) * macOS Mojave (10.14) * macOS Catalina (10.15) * macOS Big Sur (11.0) * macOS Monterey (12.0) * macOS Ventura (13.0) +* macOS Sonoma (14.0) Keyman should also work on future releases of macOS, even if they are not yet listed here. ## Resource Requirements Keyman for macOS has minimal resource requirements. Any computer running -Mac OS X 10.10 or later should be able to run Keyman for macOS without trouble. +Mac OS X 10.13 or later should be able to run Keyman for macOS without trouble. diff --git a/mac/help/common/os.md b/mac/help/common/os.md index e0c05096c43..4362eafaa73 100644 --- a/mac/help/common/os.md +++ b/mac/help/common/os.md @@ -6,10 +6,10 @@ title: Which versions does Keyman for macOS work with? Keyman supports the following Mac operating systems: -* Mac OS X Yosemite (10.10) -* Mac OS X El Capitan (10.11) -* macOS Sierra (10.12) * macOS High Sierra (10.13) * macOS Mojave (10.14) * macOS Catalina (10.15) * macOS Big Sur (11.0) +* macOS Monterey (12.0) +* macOS Ventura (13.0) +* macOS Sonoma (14.0) diff --git a/mac/help/common/requirements.md b/mac/help/common/requirements.md index b54aa64bfde..8b1c5f77592 100644 --- a/mac/help/common/requirements.md +++ b/mac/help/common/requirements.md @@ -3,4 +3,4 @@ title: What are Keyman's hardware requirements? --- Keyman for macOS has minimal resource requirements. Any computer running -Mac OS X 10.10 or later should be able to run Keyman for macOS without trouble. +Mac OS X 10.13 or later should be able to run Keyman for macOS without trouble. diff --git a/oem/firstvoices/ios/FirstVoices.xcodeproj/project.pbxproj b/oem/firstvoices/ios/FirstVoices.xcodeproj/project.pbxproj index 7925b00543a..f11f08ddbb0 100644 --- a/oem/firstvoices/ios/FirstVoices.xcodeproj/project.pbxproj +++ b/oem/firstvoices/ios/FirstVoices.xcodeproj/project.pbxproj @@ -301,9 +301,9 @@ 9889E8B31BFA9CEC00019560 /* Frameworks */, 9889E8B41BFA9CEC00019560 /* Resources */, 98C9A8C81BFBF23C009E9A4F /* Embed App Extensions */, - 37DF007C24595F6F00C73128 /* ShellScript */, - 9800EC5D1C029FCD00BF0FB5 /* ShellScript */, 371C855D2288F3A50076612A /* Embed Frameworks */, + 37DF007C24595F6F00C73128 /* Run Script */, + 9800EC5D1C029FCD00BF0FB5 /* ShellScript */, ); buildRules = ( ); @@ -439,11 +439,11 @@ outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = ". \"$KEYMAN_ROOT/resources/build/xcode-utils.sh\"\n\n# Calls resource script to update build products' versioning.\nphaseSetBundleVersions\n"; + shellPath = "/usr/bin/env bash"; + shellScript = "\"$KEYMAN_ROOT/resources/build/xcode-wrap.sh\" \"$KEYMAN_ROOT/resources/build/set-bundle-versions-untagged.sh\"\n"; showEnvVarsInLog = 0; }; - 37DF007C24595F6F00C73128 /* ShellScript */ = { + 37DF007C24595F6F00C73128 /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -452,13 +452,14 @@ ); inputPaths = ( ); + name = "Run Script"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\n. \"$KEYMAN_ROOT/resources/build/xcode-utils.sh\"\n\n# Calls resource script to update build products' versioning.\n# true: applies VERSION_WITH_TAG to custom KeymanVersionWithTag plist member used for in-app display\nphaseSetBundleVersions true\n\n# Also updates the version string for Settings\nsetSettingsBundleVersion\n"; + shellPath = "/usr/bin/env bash"; + shellScript = "\"$KEYMAN_ROOT/resources/build/xcode-wrap.sh\" \"$KEYMAN_ROOT/resources/build/set-bundle-versions-and-settings-untagged.sh\"\n"; showEnvVarsInLog = 0; }; 9800EC5D1C029FCD00BF0FB5 /* ShellScript */ = { @@ -471,8 +472,8 @@ outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "verstr1=$(/usr/libexec/PlistBuddy -c \"Print CFBundleShortVersionString\" \"$SRCROOT/FirstVoices/Info.plist\")\nverstr2=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"$SRCROOT/FirstVoices/Info.plist\")\n/usr/libexec/PlistBuddy \"$SRCROOT/Settings.bundle/Root.plist\" -c \"set PreferenceSpecifiers:0:DefaultValue $verstr1 (build $verstr2)\"\n"; + shellPath = "/usr/bin/env bash"; + shellScript = "#verstr1=$(/usr/libexec/PlistBuddy -c \"Print CFBundleShortVersionString\" \"$SRCROOT/FirstVoices/Info.plist\")\n#verstr2=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"$SRCROOT/FirstVoices/Info.plist\")\n#/usr/libexec/PlistBuddy \"$SRCROOT/Settings.bundle/Root.plist\" -c \"set PreferenceSpecifiers:0:DefaultValue $verstr1 (build $verstr2)\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ diff --git a/resources/build/sentry-dsym-upload.sh b/resources/build/sentry-dsym-upload.sh new file mode 100755 index 00000000000..47f73b7a771 --- /dev/null +++ b/resources/build/sentry-dsym-upload.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +# Calls resource script to perform the dSYM upload + +source "$KEYMAN_ROOT/resources/build/xcode-utils.sh" +phaseSentryDsymUpload "keyman-ios" \ No newline at end of file diff --git a/resources/build/set-bundle-versions-and-settings-tagged.sh b/resources/build/set-bundle-versions-and-settings-tagged.sh new file mode 100755 index 00000000000..9ad0e9758c9 --- /dev/null +++ b/resources/build/set-bundle-versions-and-settings-tagged.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +# Calls script in xcode-utils to update the version +# true: applies VERSION_WITH_TAG to custom KeymanVersionWithTag plist member used for in-app display +# updates the version string for Settings + +source "$KEYMAN_ROOT/resources/build/xcode-utils.sh" + +phaseSetBundleVersions true + +setSettingsBundleVersion \ No newline at end of file diff --git a/resources/build/set-bundle-versions-and-settings-untagged.sh b/resources/build/set-bundle-versions-and-settings-untagged.sh new file mode 100755 index 00000000000..38256c7a9a0 --- /dev/null +++ b/resources/build/set-bundle-versions-and-settings-untagged.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +# Calls script in xcode-utils to update the version +# true: applies VERSION_WITH_TAG to custom KeymanVersionWithTag plist member used for in-app display +# updates the version string for Settings + +source "$KEYMAN_ROOT/resources/build/xcode-utils.sh" + +phaseSetBundleVersions + +setSettingsBundleVersion \ No newline at end of file diff --git a/resources/build/set-bundle-versions-tagged.sh b/resources/build/set-bundle-versions-tagged.sh new file mode 100755 index 00000000000..8b4075a037b --- /dev/null +++ b/resources/build/set-bundle-versions-tagged.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +# Calls script in xcode-utils to update the version +# true: applies VERSION_WITH_TAG to custom KeymanVersionWithTag plist member used for in-app display + +source "$KEYMAN_ROOT/resources/build/xcode-utils.sh" +phaseSetBundleVersions true \ No newline at end of file diff --git a/resources/build/set-bundle-versions-untagged.sh b/resources/build/set-bundle-versions-untagged.sh new file mode 100755 index 00000000000..85ea09f9c34 --- /dev/null +++ b/resources/build/set-bundle-versions-untagged.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +# Calls script in xcode-utils to update the version + +source "$KEYMAN_ROOT/resources/build/xcode-utils.sh" +phaseSetBundleVersions \ No newline at end of file diff --git a/resources/build/xcode-utils.sh b/resources/build/xcode-utils.sh index 91fc7d43f5d..9476161ae63 100755 --- a/resources/build/xcode-utils.sh +++ b/resources/build/xcode-utils.sh @@ -7,6 +7,8 @@ # - echo "warning: foo" will generate an actual compile warning within Xcode: "foo" # - echo "error: bar" will likewise generate a compile error within Xcode: "bar" +set -e + function buildWarning() { echo "warning: $1" } diff --git a/resources/build/xcode-wrap.sh b/resources/build/xcode-wrap.sh new file mode 100755 index 00000000000..61d79ca24e9 --- /dev/null +++ b/resources/build/xcode-wrap.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +echo "wrap script for arch $(arch)" +if [[ $(arch) == i386 ]] && [[ -f /usr/local/bin/bash ]]; then + /usr/local/bin/bash -l "$@" || exit $? +elif [[ $(arch) == arm64 ]] && [[ -f /opt/homebrew/bin/bash ]]; then + /opt/homebrew/bin/bash -l "$@" || exit $? +else + >&2 echo "Could not start build due to missing homebrew bash" + exit 55 +fi \ No newline at end of file diff --git a/resources/devbox/macos/macos.sh b/resources/devbox/macos/macos.sh index edbfb43a6fc..5a3914d9a76 100755 --- a/resources/devbox/macos/macos.sh +++ b/resources/devbox/macos/macos.sh @@ -145,7 +145,7 @@ which brew || ( ## Install devchain components BREW_ALL="bash jq python3 meson ninja coreutils pyenv" -BREW_WEB="node emscripten openjdk@8" +BREW_WEB="node emscripten" BREW_IOS="swiftlint carthage" BREW_MACOS="carthage cocoapods" BREW_ANDROID="openjdk@8 android-sdk android-studio ant gradle maven" diff --git a/web/src/engine/osk/src/input/gestures/specsForLayout.ts b/web/src/engine/osk/src/input/gestures/specsForLayout.ts index 4fb8806e267..213278277e4 100644 --- a/web/src/engine/osk/src/input/gestures/specsForLayout.ts +++ b/web/src/engine/osk/src/input/gestures/specsForLayout.ts @@ -31,10 +31,16 @@ export interface GestureParams { */ permitsFlick: (item?: Item) => boolean, + /** + * The minimum _net_ distance traveled before a longpress flick-shortcut will cancel any + * conflicting flick models. + */ + flickDistStart: number, + /** * The minimum _net_ distance traveled before a longpress flick-shortcut will trigger. */ - flickDist: number, + flickDistFinal: number, /** * The maximum amount of raw-distance movement allowed for a longpress before it is @@ -104,7 +110,8 @@ export const DEFAULT_GESTURE_PARAMS: GestureParams = { permitsFlick: () => true, // Note: actual runtime value is determined at runtime based upon row height. // See `VisualKeyboard.refreshLayout`, CTRL-F "Step 3". - flickDist: 5, + flickDistStart: 8, + flickDistFinal: 40, waitLength: 500, noiseTolerance: 10 }, @@ -349,11 +356,34 @@ export function instantContactResolutionModel(): ContactModel { }; } -export function flickStartContactModel(params: GestureParams): ContactModel { +export function flickStartContactModel(params: GestureParams): gestures.specs.ContactModel { + const flickParams = params.flick; + return { itemPriority: 1, pathModel: { - evaluate: (path) => path.stats.netDistance > params.flick.startDist ? 'resolve' : null + evaluate: (path, _, item) => { + const stats = path.stats; + const keySpec = item?.key.spec; + + if(keySpec && keySpec.sk) { + const flickSpec = keySpec.flick; + const hasUpFlick = flickSpec.nw || flickSpec.n || flickSpec.ne; + + if(!hasUpFlick) { + // Check for possible conflict with the longpress up-flick shortcut; + // it's supported on this key, as there is no true northish flick. + const baseDistance = stats.netDistance; + const angle = stats.angle; // from <0, -1> (straight up) going clockwise. + const verticalDistance = baseDistance * Math.cos(angle); + if(verticalDistance > params.longpress.flickDistStart) { + return 'reject'; + } + } + } + + return stats.netDistance > flickParams.startDist ? 'resolve' : null; + } }, pathResolutionAction: 'resolve', pathInheritance: 'partial' @@ -464,7 +494,10 @@ export function longpressContactModel(params: GestureParams, enabledFlicks: bool * each side of due N in total. */ if((enabledFlicks && spec.permitsFlick(stats.lastSample.item)) && (stats.cardinalDirection?.indexOf('n') != -1 ?? false)) { - if(stats.netDistance > spec.flickDist) { + const baseDistance = stats.netDistance; + const angle = stats.angle; // from <0, -1> (straight up) going clockwise. + const verticalDistance = baseDistance * Math.cos(angle); + if(verticalDistance > spec.flickDistFinal) { return 'resolve'; } } else if(resetForRoaming) { diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index 1f454bfd742..033abc2c454 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -1301,11 +1301,20 @@ export default class VisualKeyboard extends EventEmitter implements Ke Note: longpress.flickDist needs to be no greater than flick.startDist. Otherwise, the longpress up-flick shortcut will not work on keys that support flick gestures. (Such as sil_euro_latin 3.0+) + + Since it's also based on the purely northward component, it's best to + have it be slightly lower. 80% of flick.startDist gives a range of + about 37 degrees to each side before a flick-start would win, while + 70.7% gives 45 degrees. + + (The range _will_ be notably tighter on keys with both longpresses and + flicks as a result.) */ - this.gestureParams.longpress.flickDist = 0.25 * this.currentLayer.rowHeight; - this.gestureParams.flick.startDist = 0.25 * this.currentLayer.rowHeight; - this.gestureParams.flick.dirLockDist = 0.35 * this.currentLayer.rowHeight; - this.gestureParams.flick.triggerDist = 0.75 * this.currentLayer.rowHeight; + this.gestureParams.longpress.flickDistStart = 0.24 * this.currentLayer.rowHeight; + this.gestureParams.flick.startDist = 0.30 * this.currentLayer.rowHeight; + this.gestureParams.flick.dirLockDist = 0.35 * this.currentLayer.rowHeight; + this.gestureParams.flick.triggerDist = 0.75 * this.currentLayer.rowHeight; + this.gestureParams.longpress.flickDistFinal = 0.75 * this.currentLayer.rowHeight; } // Phase 4: Refresh the layout of the layer-group and active layer. diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index 2fa2307a339..11a8ef02293 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -1186,7 +1186,10 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to Please select a Keyman keyboard to find related fonts. - + + + + No fonts have been found as suggestions for Keyboard %1$s. diff --git a/windows/src/engine/keyman/viskbd/UfrmOSKFontHelper.pas b/windows/src/engine/keyman/viskbd/UfrmOSKFontHelper.pas index 02da0309ced..cefd0ce01a4 100644 --- a/windows/src/engine/keyman/viskbd/UfrmOSKFontHelper.pas +++ b/windows/src/engine/keyman/viskbd/UfrmOSKFontHelper.pas @@ -1,18 +1,18 @@ (* Name: UfrmOSKFontHelper Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 27 Mar 2008 Modified Date: 25 Sep 2014 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 27 Mar 2008 - mcdurdin - Initial version I1374 20 Jul 2008 - mcdurdin - I1533 - Show hint for non-Unicode keyboards 29 Mar 2010 - mcdurdin - I2199 - Shift+click @@ -413,6 +413,11 @@ procedure TfrmOSKFontHelper.FormatGrid; i: Integer; m: Integer; begin + if FSelectedKeyboard.Fonts.Count = 0 then + begin + Exit; // Exit as grid needs at least one font + end; + grid.DefaultColWidth := tbSize.Position; grid.DefaultRowHeight := tbSize.Position; @@ -447,39 +452,47 @@ procedure TfrmOSKFontHelper.DisplayKeyboardFonts; FKeyboard := FCheckFontKeyboards.Keyboards[FLastSelectedKeyboardID]; if (FLastSelectedKeyboardID <> '') and Assigned(FKeyboard) then begin - SetLength(FChars, Length(FKeyboard.Chars)); - J := 0; - I := 1; - while I <= Length(FKeyboard.Chars) do // I2712 + if FKeyboard.Fonts.Count > 0 then begin - ch := FKeyboard.Chars[I]; - if Uni_IsSurrogate1(ch) and (I < Length(FKeyboard.Chars)) and Uni_IsSurrogate2(FKeyboard.Chars[I+1]) then + SetLength(FChars, Length(FKeyboard.Chars)); + J := 0; + I := 1; + while I <= Length(FKeyboard.Chars) do // I2712 begin - ch2 := FKeyboard.Chars[I+1]; - FChars[J] := Uni_SurrogateToUTF32(ch, ch2); + ch := FKeyboard.Chars[I]; + if Uni_IsSurrogate1(ch) and (I < Length(FKeyboard.Chars)) and Uni_IsSurrogate2(FKeyboard.Chars[I+1]) then + begin + ch2 := FKeyboard.Chars[I+1]; + FChars[J] := Uni_SurrogateToUTF32(ch, ch2); + Inc(I); + end + else + FChars[J] := Ord(ch); Inc(I); - end - else - FChars[J] := Ord(ch); - Inc(I); - Inc(J); - end; + Inc(J); + end; - SetLength(FChars, J); + SetLength(FChars, J); - grid.ColCount := Length(FChars) + 2; - grid.RowCount := FKeyboard.Fonts.Count; + grid.ColCount := Length(FChars) + 2; + grid.RowCount := FKeyboard.Fonts.Count; - for i := 0 to FKeyboard.Fonts.Count - 1 do - if FKeyboard.Fonts[i].Coverage < 50 then - begin - grid.RowCount := i; - Break; - end; + for i := 0 to FKeyboard.Fonts.Count - 1 do + if FKeyboard.Fonts[i].Coverage < 50 then + begin + grid.RowCount := i; + Break; + end; - FSelectedKeyboard := FKeyboard; - FormatGrid; - SetDisplay(''); + FSelectedKeyboard := FKeyboard; + FormatGrid; + SetDisplay(''); + end + else + begin + FSelectedKeyboard := FKeyboard; + SetDisplay(MsgFromIdFormat(S_OSK_FontHelper_NoFonts, [FSelectedKeyboard.Name])); + end; end else begin