diff --git a/linux/keyman-config/keyman_config/kvk2ldml.py b/linux/keyman-config/keyman_config/kvk2ldml.py index fcd93e9f0b..9d1bf78741 100755 --- a/linux/keyman-config/keyman_config/kvk2ldml.py +++ b/linux/keyman-config/keyman_config/kvk2ldml.py @@ -100,65 +100,64 @@ KVKS_RALT= b'\x40' # from web/source/kmwosk.ts VKey_to_Iso = { - 90 : "B01", # Z - 88 : "B02", # X - 67 : "B03", # C - 86 : "B04", # V - 66 : "B05", # B - 78 : "B06", # N - 77 : "B07", # M - 188 : "B08", # , - 190 : "B09", # . - 191 : "B10", # / - 65 : "C01", # A - 83 : "C02", # S - 68 : "C03", # D - 70 : "C04", # F - 71 : "C05", # G - 72 : "C06", # H - 74 : "C07", # J - 75 : "C08", # K - 76 : "C09", # L - 186 : "C10", # ; - 222 : "C11", # ' - 81 : "D01", # Q - 87 : "D02", # W - 69 : "D03", # E - 82 : "D04", # R - 84 : "D05", # T - 89 : "D06", # Y - 85 : "D07", # U - 73 : "D08", # I - 79 : "D09", # O - 80 : "D10", # P - 219 : "D11", # [ - 221: "D12", # ] - 49 : "E01", # 1 - 50 : "E02", # 2 - 51 : "E03", # 3 - 52 : "E04", # 4 - 53 : "E05", # 5 - 54 : "E06", # 6 - 55 : "E07", # 7 - 56 : "E08", # 8 - 57 : "E09", # 9 - 48 : "E10", # 0 - 189 : "E11", # - - 187 : "E12", # = - 192 : "E00", # ` - 220 : "B00", # \ - 226 : "C12", # extra key on european keyboards - 32 : "A03", # space - 96 : "A51", # "K_NP0" - 97 : "B51", # "K_NP1" - 98 : "B52", # "K_NP2" - 99 : "B53", # "K_NP3" - 100 : "C51", # "K_NP4" - 101 : "C52", # "K_NP5" - 102 : "C53", # "K_NP6" - 103 : "D51", # "K_NP7" - 104 : "D52", # "K_NP8" - 105 : "D53" # "K_NP9" + 90 : { "code": "B01", "base" : "z", "shift" : "Z" }, # Z + 88 : { "code": "B02", "base" : "x", "shift" : "X" }, # X + 67 : { "code": "B03", "base" : "c", "shift" : "C" }, # C + 86 : { "code": "B04", "base" : "v", "shift" : "V" }, # V + 66 : { "code": "B05", "base" : "b", "shift" : "B" }, # B + 78 : { "code": "B06", "base" : "n", "shift" : "N" }, # N + 77 : { "code": "B07", "base" : "m", "shift" : "M" }, # M + 188 : { "code": "B08", "base" : ",", "shift" : "<" }, # , + 190 : { "code": "B09", "base" : ".", "shift" : ">" }, # . + 191 : { "code": "B10", "base" : "/", "shift" : "?" }, # / + 65 : { "code": "C01", "base" : "a", "shift" : "A" }, # A + 83 : { "code": "C02", "base" : "s", "shift" : "S" }, # S + 68 : { "code": "C03", "base" : "d", "shift" : "D" }, # D + 70 : { "code": "C04", "base" : "f", "shift" : "F" }, # F + 71 : { "code": "C05", "base" : "g", "shift" : "G" }, # G + 72 : { "code": "C06", "base" : "h", "shift" : "H" }, # H + 74 : { "code": "C07", "base" : "j", "shift" : "J" }, # J + 75 : { "code": "C08", "base" : "k", "shift" : "K" }, # K + 76 : { "code": "C09", "base" : "l", "shift" : "L" }, # L + 186 : { "code": "C10", "base" : ";", "shift" : ":" }, # ; + 222 : { "code": "C11", "base" : "'", "shift" : '"' }, # ' + 81 : { "code": "D01", "base" : "q", "shift" : "Q" }, # Q + 87 : { "code": "D02", "base" : "w", "shift" : "W" }, # W + 69 : { "code": "D03", "base" : "e", "shift" : "E" }, # E + 82 : { "code": "D04", "base" : "r", "shift" : "R" }, # R + 84 : { "code": "D05", "base" : "t", "shift" : "T" }, # T + 89 : { "code": "D06", "base" : "y", "shift" : "Y" }, # Y + 85 : { "code": "D07", "base" : "u", "shift" : "U" }, # U + 73 : { "code": "D08", "base" : "i", "shift" : "I" }, # I + 79 : { "code": "D09", "base" : "o", "shift" : "O" }, # O + 80 : { "code": "D10", "base" : "p", "shift" : "P" }, # P + 219 : { "code": "D11", "base" : "[", "shift" : "{" }, # [ + 221 : { "code": "D12", "base" : "]", "shift" : "}" }, # ] + 49 : { "code": "E01", "base" : "1", "shift" : "!" }, # 1 + 50 : { "code": "E02", "base" : "2", "shift" : "@" }, # 2 + 51 : { "code": "E03", "base" : "3", "shift" : "#" }, # 3 + 52 : { "code": "E04", "base" : "4", "shift" : "$" }, # 4 + 53 : { "code": "E05", "base" : "5", "shift" : "%" }, # 5 + 54 : { "code": "E06", "base" : "6", "shift" : "^" }, # 6 + 55 : { "code": "E07", "base" : "7", "shift" : "&" }, # 7 + 56 : { "code": "E08", "base" : "8", "shift" : "*" }, # 8 + 57 : { "code": "E09", "base" : "9", "shift" : "(" }, # 9 + 48 : { "code": "E10", "base" : "0", "shift" : ")" }, # 0 + 189 : { "code": "E11", "base" : "-", "shift" : "_" }, # - + 187 : { "code": "E12", "base" : "=", "shift" : "+" }, # = + 192 : { "code": "E00", "base" : "`", "shift" : "~" }, # ` + 220 : { "code": "C12", "base" : "\\","shift" : "|" }, # \ + 226 : { "code": "B00", "base" : "<", "shift" : ">" }, # extra key on european keyboards + 32 : { "code": "A03", "base" : " ", "shift" : " " }, # space + 97 : { "code": "B51", "base" : "1", "shift" : "1" }, # "K_NP1" + 98 : { "code": "B52", "base" : "2", "shift" : "2" }, # "K_NP2" + 99 : { "code": "B53", "base" : "3", "shift" : "3" }, # "K_NP3" + 100 : { "code": "C51", "base" : "4", "shift" : "4" }, # "K_NP4" + 101 : { "code": "C52", "base" : "5", "shift" : "5" }, # "K_NP5" + 102 : { "code": "C53", "base" : "6", "shift" : "6" }, # "K_NP6" + 103 : { "code": "D51", "base" : "7", "shift" : "7" }, # "K_NP7" + 104 : { "code": "D52", "base" : "8", "shift" : "8" }, # "K_NP8" + 105 : { "code": "D53", "base" : "9", "shift" : "9" }, # "K_NP9" } @@ -309,8 +308,34 @@ def convert_ldml(kvkData): else: keymaps[modifier] = (key,) + for vkey in VKey_to_Iso: + alreadyused = False + for key in keymaps["None"]: + if key.VKey == vkey: + alreadyused = True + if not alreadyused and vkey != 226: + uskey = NKey() + uskey.VKey = vkey + uskey.text = VKey_to_Iso[vkey]["base"] + if "None" in keymaps: + keymaps["None"] = keymaps["None"] + (uskey,) + else: + keymaps["None"] = (uskey,) + alreadyused = False + for key in keymaps["shift"]: + if key.VKey == vkey: + alreadyused = True + if not alreadyused and vkey != 226: + uskey = NKey() + uskey.VKey = vkey + uskey.text = VKey_to_Iso[vkey]["shift"] + if "shift" in keymaps: + keymaps["shift"] = keymaps["shift"] + (uskey,) + else: + keymaps["shift"] = (uskey,) + ldml = etree.Element("keyboard", locale = "zzz-keyman") - etree.SubElement(ldml, "version", platform = "10") + etree.SubElement(ldml, "version", platform = "11") names = etree.SubElement(ldml, "names") names.append( etree.Element("name", value = "ZZZ") ) @@ -321,7 +346,7 @@ def convert_ldml(kvkData): keymap = etree.SubElement(ldml, "keyMap", modifiers = modifier) for key in keymaps[modifier]: if key.VKey in VKey_to_Iso: - iso_key = VKey_to_Iso[key.VKey] + iso_key = VKey_to_Iso[key.VKey]["code"] keymap.append( etree.Element("map", iso = iso_key, to = key.text) ) else: logging.warning("Unknown vkey: %s", key.VKey) diff --git a/web/source/kmwosk.ts b/web/source/kmwosk.ts index bbcdfac6d0..e0a48a7283 100644 --- a/web/source/kmwosk.ts +++ b/web/source/kmwosk.ts @@ -7,8 +7,11 @@ namespace com.keyman { text?: string; sp?: string; width: string; + layer?: string; // Added during OSK construction. nextlayer?: string; pad?: string; + widthpc?: number; // Added during OSK construction. + padpc?: number; // Added during OSK construction. constructor(id: string, text?: string, width?: string, sp?: string, nextlayer?: string, pad?: string) { this.id = id; @@ -19,6 +22,289 @@ namespace com.keyman { this.pad = pad; } } + + export abstract class OSKKey { + spec: OSKKeySpec; + + constructor(spec: OSKKeySpec) { + this.spec = spec; + } + + abstract getId(): string; + + /** + * Replace default key names by special font codes for modifier keys + * + * @param {string} oldText + * @return {string} + **/ + protected renameSpecialKey(oldText: string): string { + let keyman = (window['keyman']) + // If a 'special key' mapping exists for the text, replace it with its corresponding special OSK character. + let specialCharacters = keyman['osk'].specialCharacters; + return specialCharacters[oldText] ? String.fromCharCode(0XE000 + specialCharacters[oldText]) : oldText; + } + + // Produces a HTMLSpanElement with the key's actual text. + protected generateKeyText(): HTMLSpanElement { + let util = (window['keyman']).util; + let spec = this.spec; + + // Add OSK key labels + var t=util._CreateElement('span'), ts=t.style; + if(spec['text'] == null || spec['text'] == '') { + t.innerHTML='\xa0'; // default: nbsp. + if(typeof spec['id'] == 'string') { + // If the ID's Unicode-based, just use that code. + if(/^U_[0-9A-F]{4}$/i.test(spec['id'])) { + t.innerHTML=String.fromCharCode(parseInt(spec['id'].substr(2),16)); + } + } + } else { + t.innerHTML=spec['text']; + } + t.className='kmw-key-text'; + + // Use special case lookup for modifier keys + if(spec['sp'] == '1' || spec['sp'] == '2') { + // Unique layer-based transformation. + var tId=((spec['text'] == '*Tab*' && spec.layer == 'shift') ? '*TabLeft*' : spec['text']); + + // Transforms our *___* special key codes into their corresponding PUA character codes for keyboard display. + t.innerHTML=this.renameSpecialKey(tId); + } + + //Override font spec if set for this key in the layout + ts.fontSize=(window['keyman']).osk.fontSize; //Build 344, KMEW-90 + if(typeof spec['font'] == 'string' && spec['font'] != '') { + ts.fontFamily=spec['font']; + } + if(typeof spec['fontsize'] == 'string' && spec['fontsize'] != 0) { + ts.fontSize=spec['fontsize']; + } + + return t; + } + } + + export class OSKBaseKey extends OSKKey { + constructor(spec: OSKKeySpec) { + super(spec); + } + + getId(): string { + // Define each key element id by layer id and key id (duplicate possible for SHIFT - does it matter?) + return this.spec.layer+'-'+this.spec.id; + } + + // Produces a small reference label for the corresponding physical key on a US keyboard. + private generateKeyCapLabel(): HTMLDivElement { + // Create the default key cap labels (letter keys, etc.) + var x = (window['keyman'])['osk'].keyCodes[this.spec.id]; + switch(x) { + // Converts the keyman key id code for common symbol keys into its representative ASCII code. + // K_COLON -> K_BKQUOTE + case 186: x=59; break; + case 187: x=61; break; + case 188: x=44; break; + case 189: x=45; break; + case 190: x=46; break; + case 191: x=47; break; + case 192: x=96; break; + // K_LBRKT -> K_QUOTE + case 219: x=91; break; + case 220: x=92; break; + case 221: x=93; break; + case 222: x=39; break; + default: + // No other symbol character represents a base key on the standard QWERTY English layout. + if(x < 48 || x > 90) { + x=0; + } + } + + if(x > 0) { + let q = (window['keyman']).util._CreateElement('div'); + q.className='kmw-key-label'; + q.innerHTML=String.fromCharCode(x); + return q; + } else { + // Keyman-only virtual keys have no corresponding physical key. + return null; + } + } + + private processSubkeys(btn: HTMLDivElement) { + // Add reference to subkey array if defined + var bsn: number, bsk=btn['subKeys'] = this.spec['sk']; + // Transform any special keys into their PUA representations. + for(bsn=0; bsnwindow['keyman']).util._CreateElement('div'); + skIcon.className='kmw-key-popup-icon'; + //kDiv.appendChild(skIcon); + btn.appendChild(skIcon); + } + + construct(layout, layerId: string, rowStyle: CSSStyleDeclaration, totalPercent: number): {element: HTMLDivElement, percent: number} { + let util = (window['keyman']).util; + let osk = (window['keyman']).osk; + let spec = this.spec; + let isDesktop = util.device.formFactor == 'desktop' + + spec.layer = layerId; + + let kDiv=util._CreateElement('div'); + kDiv['keyId']=spec['id']; + kDiv.className='kmw-key-square'; + + let ks=kDiv.style; + ks.width=this.objectUnits(spec['widthpc']); + + let originalPercent = totalPercent; + + let btn=util._CreateElement('div'); + // Set button class + osk.setButtonClass(spec,btn,layout); + + // Set key and button positioning properties. + if(!isDesktop) { + // Regularize interkey spacing by rounding key width and padding (Build 390) + ks.left=this.objectUnits(totalPercent+spec['padpc']); + ks.bottom=rowStyle.bottom; + ks.height=rowStyle.height; //must be specified in px for rest of layout to work correctly + + // Set distinct phone and tablet button position properties + btn.style.left=ks.left; + btn.style.width=ks.width; + } else { + ks.marginLeft=this.objectUnits(spec['padpc']); + } + + totalPercent=totalPercent+spec['padpc']+spec['widthpc']; + + // Add the (US English) keycap label for desktop OSK or if KDU flag is non-zero + if(layout.keyLabels || isDesktop) { + let keyCap = this.generateKeyCapLabel(); + + if(keyCap) { + btn.appendChild(keyCap); + } + } + + // Define each key element id by layer id and key id (duplicate possible for SHIFT - does it matter?) + btn.id=this.getId(); + // Keyman 12 goal: convert btn['key'] to use the 'this' reference instead. + btn['key']=spec; //attach reference to key layout spec to element + + // Define callbacks to handle key touches: iOS and Android tablets and phones + // TODO: replace inline function calls?? + if(!util.device.touchable) { + // Highlight key while mouse down or if moving back over originally selected key + btn.onmouseover=btn.onmousedown=osk.mouseOverMouseDownHandler; // Build 360 + + // Remove highlighting when key released or moving off selected element + btn.onmouseup=btn.onmouseout=osk.mouseUpMouseOutHandler; //Build 360 + } + + // Handle subkey-related tasks. + if(typeof(spec['sk']) != 'undefined' && spec['sk'] != null) { + this.processSubkeys(btn); + } else { + btn['subKeys']=null; + } + + // Add text to button and button to placeholder div + btn.appendChild(this.generateKeyText()); + kDiv.appendChild(btn); + + // Prevent user selection of key captions + //t.style.webkitUserSelect='none'; + + // The 'return value' of this process. + return {element: kDiv, percent: totalPercent - originalPercent}; + } + + objectUnits(v: number) { + if((window['keyman']).util.device.formFactor == 'desktop') { + return v + '%'; + } else { + return Math.round(v)+'px'; + } + } + } + + export class OSKSubKey extends OSKKey { + constructor(spec: OSKKeySpec) { + super(spec); + } + + getId(): string { + let spec = this.spec; + // Create (temporarily) unique ID by prefixing 'popup-' to actual key ID + if(typeof(spec['layer']) == 'string' && spec['layer'] != '') { + return 'popup-'+spec['layer']+'-'+spec['id']; + } else { + // We only create subkeys when they're needed - the currently-active layer should be fine. + return 'popup-' + ( window['keyman']).osk.layerId + '-'+spec['id']; + } + } + + construct(baseKey: HTMLDivElement, topMargin: boolean): HTMLDivElement { + let osk = ( window['keyman']).osk; + let spec = this.spec; + + let kDiv=document.createElement('div'); + let tKey = osk.getDefaultKeyObject(); + let ks=kDiv.style; + + for(var tp in tKey) { + if(typeof spec[tp] != 'string') { + spec[tp]=tKey[tp]; + } + } + + kDiv.className='kmw-key-square-ex'; + kDiv['keyId']=spec['id']; + if(topMargin) { + ks.marginTop='5px'; + } + + if(typeof spec['width'] != 'undefined') { + ks.width=(parseInt(spec['width'],10)*baseKey.offsetWidth/100)+'px'; + } else { + ks.width=baseKey.offsetWidth+'px'; + } + ks.height=baseKey.offsetHeight+'px'; + + let btn=document.createElement('div'); + osk.setButtonClass(spec,btn); + + btn.id = this.getId(); + + // Plan for Keyman 12: swap to use the 'this' reference. + btn['key'] = spec; + + // Must set button size (in px) dynamically, not from CSS + let bs=btn.style; + bs.height=ks.height; + bs.width=ks.width; + + // Must set position explicitly, at least for Android + bs.position='absolute'; + + btn.appendChild(this.generateKeyText()); + kDiv.appendChild(btn); + + return kDiv; + } + } } /*** @@ -588,7 +874,7 @@ if(!window['keyman']['initialized']) { // The holder is position:fixed, but the keys do not need to be, as no scrolling // is possible while the array is visible. So it is simplest to let the keys have // position:static and display:inline-block - var subKeys=document.createElement('DIV'),i,sk, + var subKeys=document.createElement('DIV'),i, t,ts,t1,ts1,kDiv,ks,btn,bs; var tKey = osk.getDefaultKeyObject(); @@ -640,72 +926,15 @@ if(!window['keyman']['initialized']) { // Add nested button elements for each sub-key for(i=0; i 1 && nRow > 0) { - ks.marginTop='5px'; + needsTopMargin = true; } - if(typeof sk['width'] != 'undefined') { - kDiv.width=ks.width=(parseInt(sk['width'],10)*e.offsetWidth/100)+'px'; - } else { - kDiv.width=ks.width=e.offsetWidth+'px'; - } - ks.height=e.offsetHeight+'px'; - - btn=document.createElement('DIV'); - osk.setButtonClass(sk,btn); - - // Create (temporarily) unique ID by prefixing 'popup-' to actual key ID - if(typeof(sk['layer']) == 'string' && sk['layer'] != '') { - btn.id='popup-'+sk['layer']+'-'+sk['id']; - } else { - btn.id='popup-' + osk.layerId + '-'+sk['id']; - } - - btn.key = sk; - - // Must set button size (in px) dynamically, not from CSS - bs=btn.style; bs.height=ks.height; bs.width=ks.width; - - // Must set position explicitly, at least for Android - bs.position='absolute'; - t=util._CreateElement('SPAN'); - t.className='kmw-key-text'; - if(sk['text'] == null || sk['text'] == '') { - t.innerHTML='\xa0'; - if(typeof sk['id'] == 'string') { - if(/^U_[0-9A-F]{4}$/i.test(sk['id'])) { - t.innerHTML=String.fromCharCode(parseInt(sk['id'].substr(2),16)); - } - } - } else { - t.innerHTML=sk['text']; - } - - // Override the font name and size if set in the layout - ts=t.style; - ts.fontSize=osk.fontSize; //Build 344, KMEW-90 - if(typeof sk['font'] == 'string' && sk['font'] != '') { - ts.fontFamily=sk['font']; - } - if(typeof sk['fontsize'] == 'string' && sk['fontsize'] != 0) { - ts.fontSize=sk['fontsize']; - } - - btn.appendChild(t); - kDiv.appendChild(btn); + let keyGenerator = new com.keyman.OSKSubKey(e.subKeys[i]); + let kDiv = keyGenerator.construct(e, needsTopMargin); + subKeys.appendChild(kDiv); } @@ -2209,12 +2438,10 @@ if(!window['keyman']['initialized']) { } // Get the actual available document width and scale factor according to device type - var objectUnits, objectWidth; + var objectWidth; if(formFactor == 'desktop') { - objectUnits = function(v) { return v + '%' }; objectWidth = 100; } else { - objectUnits = function(v) { return Math.round(v)+'px' }; objectWidth = osk.getWidth(); } @@ -2309,8 +2536,7 @@ if(!window['keyman']['initialized']) { for(j=0; j 90) x=0; - } - - if(x > 0) - { - q=util._CreateElement('DIV'); - q.className='kmw-key-label'; - q.innerHTML=String.fromCharCode(x); - //kDiv.appendChild(q); - btn.appendChild(q); - } - } - - // Define each key element id by layer id and key id (duplicate possible for SHIFT - does it matter?) - btn.id=layer['id']+'-'+key.id; - btn.key=key; //attach reference to key layout spec to element - - // Add reference to subkey array if defined - if(typeof key['sk'] != 'undefined' && key['sk'] != null) - { - var bsn,bsk=btn.subKeys=key['sk']; - for(bsn=0; bsn '' then + begin + v := o.Values[TKeyboardInfoFile.SScriptName]; + if not Assigned(v) then + o.AddPair(TKeyboardInfoFile.SScriptName, scriptName); + end; + + if regionName <> '' then + begin + v := o.Values[TKeyboardInfoFile.SRegionName]; + if not Assigned(v) then + o.AddPair(TKeyboardInfoFile.SRegionName, regionName); + end; +end; + +procedure TMergeKeyboardInfo.CheckOrMigrateLanguages; +var + v: TJSONValue; + alangs: TJSONArray; + olangs, o: TJSONObject; + pair: TJSONPair; + i: Integer; + id: string; +begin + v := json.GetValue(TKeyboardInfoFile.SLanguages); + + if v is TJSONArray then + begin + // Migrate languages[] array to Object + alangs := v as TJSONArray; + olangs := TJSONObject.Create; + for i := 0 to alangs.Count - 1 do + begin + id := alangs.Items[i].Value; + if id = '' then + continue; + + // Populate subtag names + o := TJSONObject.Create; + olangs.AddPair(id, o); + AddSubtagNames(id, o); + end; + + json.RemovePair(TKeyboardInfoFile.SLanguages); + json.AddPair(TKeyboardInfoFile.SLanguages, olangs); + end + else if v is TJSONObject then + begin + olangs := v as TJsonObject; + for i := 0 to olangs.Count - 1 do + begin + pair := olangs.Pairs[i]; + id := pair.JSONString.Value; + if id = '' then + continue; + + // Populate subtag names + o := pair.JsonValue as TJSONObject; + AddSubtagNames(id, o); + end; + end; +end; + // // name -- from kmp.inf, js // diff --git a/windows/src/developer/history.md b/windows/src/developer/history.md index f62d6485d5..872906b86d 100644 --- a/windows/src/developer/history.md +++ b/windows/src/developer/history.md @@ -10,6 +10,7 @@ * Opening or creating a project now closes current editor files (#1242) * Projects can now include other related files such as history.md (#1243) * Keyman Developer now treats files as UTF-8 by default (#1244) +* Update kmcomp to add language subtag names to keyboard_info files (#1426) ## 2018-11-28 10.0.1206 stable * Add parameter `-add-help-link` to kmcomp (#1346) diff --git a/windows/src/engine/keyman/UfrmKeyman7Main.pas b/windows/src/engine/keyman/UfrmKeyman7Main.pas index af78a66f57..8c198f699f 100644 --- a/windows/src/engine/keyman/UfrmKeyman7Main.pas +++ b/windows/src/engine/keyman/UfrmKeyman7Main.pas @@ -155,6 +155,7 @@ uses //TOUCH UfrmTouchKeyboard, GlobalKeyboardChangeManager, UfrmVisualKeyboard, + IntegerList, KeymanTrayIcon, KeymanMenuItem, custinterfaces, @@ -220,6 +221,8 @@ type FGlobalKeyboardChangeManager: TGlobalKeyboardChangeManager; // I4271 FActiveHKL: Integer; FTrayIcon: TIcon; // I4359 + FHotkeyWindow: HWND; + FHotkeys: TIntegerList; //TOUCH FCurrentContext: string; function AddTaskbarIcon: Boolean; @@ -236,7 +239,7 @@ type procedure TrayIconMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure ShowBalloon(Value: Integer); - procedure DoHotkey(Target: Integer); + procedure DoInterfaceHotkey(Target: Integer); procedure WMUserStart(var Message: TMessage); message WM_USER_Start; procedure WMUserParameterPass(var Message: TMessage); message WM_USER_ParameterPass; @@ -286,8 +289,11 @@ type procedure UpdateFocusInfo; // I4731 procedure UnregisterControllerWindows; // I4731 function IsSysTrayWindow(AHandle: THandle): Boolean; - procedure HandleLanguageHotkey(HotkeyValue: Integer); procedure GetTrayIconHandle; // I4731 + procedure RegisterHotkeys; + procedure UnregisterHotkeys; + procedure HotkeyWndProc(var Message: TMessage); + procedure DoLanguageHotkey(Index: Integer); protected procedure Notification(AComponent: TComponent; Operation: TOperation); override; @@ -399,7 +405,6 @@ uses Vcl.AxCtrls, Vcl.Buttons, Vcl.ComCtrls, - IntegerList, InterfaceHotkeys, utilhotkey, MessageIdentifiers, @@ -493,7 +498,6 @@ begin FLangSwitchManager := TLangSwitchManager.Create; // I3933 - //FLastKeymanID := -1; FLastHKL := GetKeyboardLayout(0); @@ -533,6 +537,8 @@ procedure TfrmKeyman7Main.FormDestroy(Sender: TObject); begin ClosePlatformComms64; + UnregisterHotkeys; + with TRegistryErrorControlled.Create do // I2890 try if OpenKey(SRegKey_KeymanOSK_CU, True) then @@ -560,6 +566,7 @@ begin kmint.KeymanEngineControl.ShutdownKeyman32Engine; FreeAndNil(FLangSwitchManager); // I3933 + //Windows.MessageBox(Handle, PChar(IntToStr(kmcom._AddRef)), 'RefCount+1', MB_OK); kmint.kmcom := nil; // I5132 @@ -785,13 +792,9 @@ begin UpdateFocusInfo; // I4731 RequestCurrentActiveKeyboard(0); // I3961 end; - KMC_LANGUAGEHOTKEY: // I4451 - begin - HandleLanguageHotkey(lParam); - end; KMC_INTERFACEHOTKEY: begin - DoHotkey(wParam); + DoInterfaceHotkey(wParam); end; KMC_ONSCREENKEYBOARD: begin @@ -815,6 +818,7 @@ begin end; FOSKManuallyClosedThisSession := False; FRunningProduct.FLangSwitchConfiguration.Refresh; + RegisterHotkeys; end; KMC_NOTIFYWELCOME: // I1248 - Redesigned welcome begin @@ -844,7 +848,24 @@ begin end; end; -procedure TfrmKeyman7Main.DoHotkey(Target: Integer); +procedure TfrmKeyman7Main.DoLanguageHotkey(Index: Integer); +var + FKeyboard: TLangSwitchKeyboard; +begin + if (Index >= 0) and (Index < kmcom.Languages.Count) then + begin + FKeyboard := FLangSwitchManager.FindKeyboard(kmcom.Languages[Index].HKL, kmcom.Languages[Index].ProfileGUID); + if not Assigned(FKeyboard) then Exit; + + // Handle toggle hotkey + if (FKeyboard = FLangSwitchManager.ActiveKeyboard) and (kmcom.Options['koKeyboardHotkeysAreToggle'].Value) then + FKeyboard := FLangSwitchManager.Languages[0].Keyboards[0]; + + ActivateKeyboard(FKeyboard); + end; +end; + +procedure TfrmKeyman7Main.DoInterfaceHotkey(Target: Integer); begin if not Assigned(FRunningProduct) then Exit; @@ -1227,6 +1248,8 @@ begin kmint.KeymanEngineControl.RestartEngine; // I1486 StartPlatformComms64; + + RegisterHotkeys; end; procedure TfrmKeyman7Main.RequestCurrentActiveKeyboard(Command: WORD); // I3961 @@ -1585,26 +1608,6 @@ begin inherited Notification(AComponent, Operation); end; -procedure TfrmKeyman7Main.HandleLanguageHotkey(HotkeyValue: Integer); -var - i: Integer; - FKeyboard: TLangSwitchKeyboard; -begin - for i := 0 to kmcom.Languages.Count - 1 do - if kmcom.Languages[i].Hotkey.RawValue = HotkeyValue then - begin - FKeyboard := FLangSwitchManager.FindKeyboard(kmcom.Languages[i].HKL, kmcom.Languages[i].ProfileGUID); - if not Assigned(FKeyboard) then Exit; - - // Handle toggle hotkey - if (FKeyboard = FLangSwitchManager.ActiveKeyboard) and (kmcom.Options['koKeyboardHotkeysAreToggle'].Value) then - FKeyboard := FLangSwitchManager.Languages[0].Keyboards[0]; - - ActivateKeyboard(FKeyboard); - Exit; - end; -end; - procedure TfrmKeyman7Main.HideVisualKeyboard; begin if not Assigned(kmcom) and not StartKeymanEngine then Exit; @@ -1835,4 +1838,97 @@ begin if not ShellExecuteExW(@sei) then Exit; // log end; +procedure TfrmKeyman7Main.HotkeyWndProc(var Message: TMessage); +begin + if Message.Msg = WM_HOTKEY then + begin + KL.Log('Hotkey %d', [Message.WParam]); + if Message.WParam > kh__High + then DoLanguageHotkey(Message.WParam - kh__High - 1) + else DoInterfaceHotkey(Message.WParam); + end; + Message.Result := DefWindowProc(FHotkeyWindow, Message.Msg, Message.WParam, Message.LParam); +end; + +function KeymanHotkeyModifiersToWindowsHotkeyModifiers(v: KeymanHotkeyModifiers): Integer; +begin + Result := 0; + if (v and HK_SHIFT) = HK_SHIFT then Result := Result or MOD_SHIFT; + if (v and HK_CTRL) = HK_CTRL then Result := Result or MOD_CONTROL; + if (v and HK_ALT) = HK_ALT then Result := Result or MOD_ALT; +end; + +procedure TfrmKeyman7Main.RegisterHotkeys; +var + hk: IKeymanHotkey; + i: Integer; + language: IKeymanLanguage; + id: Integer; +begin + TDebugLogClient.Instance.WriteMessage('Enter RegisterHotkeys', []); + + if FHotkeyWindow = 0 then + FHotkeyWindow := AllocateHWnd(HotkeyWndProc); + + if not Assigned(FHotkeys) then + FHotkeys := TIntegerList.Create; + + UnregisterHotkeys; + + for i := 0 to kmcom.Hotkeys.Count - 1 do + begin + hk := kmcom.Hotkeys[i]; + if not hk.IsEmpty and (hk.VirtualKey <> 0) then + begin + // Note, if hk.VirtualKey is 0, this indicates a modifier-only hotkey such + // as Alt+Left Shift. These are handled in keyman32 k32_lowlevelkeyboardhook + // because RegisterHotkey cannot handle modifier-only hotkeys. + if RegisterHotkey(FHotkeyWindow, hk.Target, KeymanHotkeyModifiersToWindowsHotkeyModifiers(hk.Modifiers), hk.VirtualKey) then + begin + TDebugLogClient.Instance.WriteMessage('Added hotkey %d -> %x %x', [hk.Target, + KeymanHotkeyModifiersToWindowsHotkeyModifiers(hk.Modifiers), hk.VirtualKey]); + FHotkeys.Add(hk.Target) + end + else + TDebugLogClient.Instance.WriteLastError('RegisterHotkeys', 'RegisterHotkey', 'Failed to register hotkey '+IntToStr(hk.Target)); + end; + end; + + for i := 0 to kmcom.Languages.Count - 1 do + begin + language := kmcom.Languages[i]; + hk := language.Hotkey; + if Assigned(hk) and not hk.IsEmpty and (hk.VirtualKey <> 0) then + begin + id := kh__High + 1 + i; + if RegisterHotkey(FHotkeyWindow, id, KeymanHotkeyModifiersToWindowsHotkeyModifiers(hk.Modifiers), hk.VirtualKey) then + begin + TDebugLogClient.Instance.WriteMessage('Added hotkey for language %s [%d] -> %x %x', [language.LocaleName, id, + KeymanHotkeyModifiersToWindowsHotkeyModifiers(hk.Modifiers), hk.VirtualKey]); + FHotkeys.Add(id); + end + else + TDebugLogClient.Instance.WriteLastError('RegisterHotkeys', 'RegisterHotkey', 'Failed to register hotkey '+IntToStr(id)); + end; + end; +end; + +procedure TfrmKeyman7Main.UnregisterHotkeys; +var + i, hk: Integer; +begin + TDebugLogClient.Instance.WriteMessage('Enter UnregisterHotkeys', []); + + if not Assigned(FHotkeys) then + Exit; + + for i := 0 to FHotkeys.Count - 1 do + begin + hk := FHotkeys[i]; + if not UnregisterHotKey(FHotkeyWindow, hk) then + TDebugLogClient.Instance.WriteLastError('UnregisterHotkeys', 'UnregisterHotkey', 'Failed to unregister hotkey '+IntToStr(hk)); + end; + FHotkeys.Clear; +end; + end. diff --git a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp index 79f478ce33..b50e0486e4 100644 --- a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp +++ b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp @@ -143,51 +143,10 @@ LRESULT _kmnLowLevelKeyboardProc( else if (KeyLanguageSwitchPress(hs->vkCode, extended, isUp, FHotkeyShiftState)) { if (ProcessLanguageSwitchShiftKey(hs->vkCode, isUp) == 1) return 1; } - else { - - /* - Process keyboard hotkeys - */ - - DWORD hk = (DWORD)hs->vkCode | FHotkeyShiftState; - - Hotkeys *hotkeys = Hotkeys::Instance(); // I4641 - - /* - Search for an interface or language hotkey - */ - - // TODO: deprecate KeymanUIDisabled, FSingleThread - - if (hotkeys) { // I4641 - Hotkey *hotkey = hotkeys->GetHotkey(hk); - if (hotkey) { - if (isUp) { - if (hotkey->HotkeyType == hktInterface) { - SendDebugMessageFormat(0, sdmGlobal, 0, "Hotkey matched = {HotkeyValue: %x, Target: %d}", - hotkey->HotkeyValue, - hotkey->Target); - Globals::PostMasterController(wm_keyman_control, MAKELONG(KMC_INTERFACEHOTKEY, hotkey->Target), 0); - } - else { - SendDebugMessageFormat(0, sdmGlobal, 0, "Hotkey matched = {HotkeyValue: %x, hkl: %x}", - hotkey->HotkeyValue, - hotkey->hkl); - - // Send the hotkey value to the master controller, rather than the language HKL or profile GUID, because - // this is cheaper than constructing a string and posting it across. - - Globals::PostMasterController(wm_keyman_control, MAKELONG(KMC_LANGUAGEHOTKEY, 0), (LPARAM)hotkey->HotkeyValue); // I4451 - } - } - return 1; - } - } - } /* - Not a hotkey, so we will use the serialized input model + Not the language switch hotkey, so we will use the serialized input model */ diff --git a/windows/src/global/delphi/general/KeymanControlMessages.pas b/windows/src/global/delphi/general/KeymanControlMessages.pas index f8f9fa5f8f..443b333dda 100644 --- a/windows/src/global/delphi/general/KeymanControlMessages.pas +++ b/windows/src/global/delphi/general/KeymanControlMessages.pas @@ -53,7 +53,7 @@ const KMC_PROFILECHANGED = 18; // 9.0.426.0 // I3933 // KMC_KEYBOARDHOTKEY = 19; // 9.0.459.0 // I4326 Deprecated in favour of language hotkeys - KMC_LANGUAGEHOTKEY = 20; // 9.0.471.0 // I4451 + // KMC_LANGUAGEHOTKEY = 20; // 9.0.471.0 // I4451 Now using keyman.exe RegisterHotkey //TOUCH KMC_CONTEXT = 19; diff --git a/windows/src/global/delphi/general/UserMessages.pas b/windows/src/global/delphi/general/UserMessages.pas index 41ec91720e..14987304ce 100644 --- a/windows/src/global/delphi/general/UserMessages.pas +++ b/windows/src/global/delphi/general/UserMessages.pas @@ -87,3 +87,4 @@ const implementation end. + diff --git a/windows/src/global/delphi/keyboards/Keyman.System.KeyboardInfoFile.pas b/windows/src/global/delphi/keyboards/Keyman.System.KeyboardInfoFile.pas index 7e294a218c..057c360bf6 100644 --- a/windows/src/global/delphi/keyboards/Keyman.System.KeyboardInfoFile.pas +++ b/windows/src/global/delphi/keyboards/Keyman.System.KeyboardInfoFile.pas @@ -5,6 +5,10 @@ interface type TKeyboardInfoFile = record const SLanguages = 'languages'; + const SDisplayName = 'displayName'; + const SLanguageName = 'languageName'; + const SScriptName = 'scriptName'; + const SRegionName = 'regionName'; end; diff --git a/windows/src/global/inc/keymancontrol.h b/windows/src/global/inc/keymancontrol.h index 063f059a65..fe6b119988 100644 --- a/windows/src/global/inc/keymancontrol.h +++ b/windows/src/global/inc/keymancontrol.h @@ -47,8 +47,8 @@ #define KMC_PROFILECHANGED 18 // 9.0.426.0 // I3933 -#define KMC_KEYBOARDHOTKEY 19 // 9.0.459.0 // I4326 -#define KMC_LANGUAGEHOTKEY 20 // 9.0.460.0 // I4451 +//#define KMC_KEYBOARDHOTKEY 19 // 9.0.459.0 // I4326 Deprecated in favour of language hotkeys +//#define KMC_LANGUAGEHOTKEY 20 // 9.0.460.0 // I4451 Now using keyman.exe RegisterHotkey //TOUCH #define KMC_CONTEXT 19 // 9.0.450.0 #define RWM_KEYMAN_CONTROL "WM_KEYMAN_CONTROL"