Merge branch 'master' into linux-backspace

This commit is contained in:
glasseyes 2018-12-17 21:02:10 +07:00
commit 2fda69b9f2
10 changed files with 622 additions and 355 deletions

View file

@ -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)

View file

@ -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 = (<KeymanBase>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 = (<KeymanBase>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=(<KeymanBase>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 = (<KeymanBase>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 = (<KeymanBase>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; bsn<bsk.length; bsn++) {
if(bsk[bsn]['sp'] == '1' || bsk[bsn]['sp'] == '2') {
var oldText=bsk[bsn]['text'];
bsk[bsn]['text']=this.renameSpecialKey(oldText);
}
}
// If a subkey array is defined, add an icon
var skIcon=(<KeymanBase>window['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 = (<KeymanBase>window['keyman']).util;
let osk = (<KeymanBase>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((<KeymanBase>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-' + (<KeymanBase> window['keyman']).osk.layerId + '-'+spec['id'];
}
}
construct(baseKey: HTMLDivElement, topMargin: boolean): HTMLDivElement {
let osk = (<KeymanBase> 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<nKeys; i++) {
sk=e.subKeys[i];
kDiv=document.createElement('DIV');
for(var tp in tKey) {
if(typeof sk[tp] != 'string') {
sk[tp]=tKey[tp];
}
}
kDiv.className='kmw-key-square-ex';
kDiv.keyId=sk['id'];
ks=kDiv.style;
nRow=Math.floor(i/nCols);
var needsTopMargin = false;
let nRow=Math.floor(i/nCols);
if(nRows > 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<keys.length; j++)
{
key=keys[j];
for(var tp in tKey)
{
for(var tp in tKey) { // tKey = osk.getDefaultKeyObject();
if(typeof key[tp] != 'string') key[tp]=tKey[tp];
}
@ -2365,147 +2591,14 @@ if(!window['keyman']['initialized']) {
//Create the key square (an outer DIV) for each key element with padding, and an inner DIV for the button (btn)
totalPercent=0;
for(j=0; j<keys.length; j++)
{
for(j=0; j<keys.length; j++) {
key=keys[j];
kDiv=util._CreateElement('DIV');
kDiv.keyId=key['id'];
kDiv.className='kmw-key-square';
ks=kDiv.style;
var keyGenerator = new com.keyman.OSKBaseKey(key);
var keyTuple = keyGenerator.construct(layout, layer['id'], rs, totalPercent);
kDiv.width=ks.width=objectUnits(key['widthpc']);
if(formFactor != 'desktop')
{
// Regularize interkey spacing by rounding key width and padding (Build 390)
//keys[j]['padpc']=Math.round(keys[j]['padpc']);
//keys[j]['widthpc']=Math.round(keys[j]['widthpc']);
ks.left=objectUnits(totalPercent+keys[j]['padpc']);
ks.bottom=rs.bottom;
ks.height=rs.height; //must be specified in px for rest of layout to work correctly
}
else
{
ks.marginLeft=objectUnits(key['padpc']);
}
totalPercent=totalPercent+keys[j]['padpc']+keys[j]['widthpc'];
btn=util._CreateElement('DIV');
// Set button class
osk.setButtonClass(key,btn,layout);
// Set distinct phone and tablet button position properties
if(formFactor != 'desktop')
{
btn.style.left=ks.left;
btn.style.width=ks.width;
}
// Add the (US English) keycap label for desktop OSK or if KDU flag is non-zero
var q=null;
if(layout.keyLabels || (formFactor == 'desktop')) //desktop or KDU flag set
{
// Create the default key cap labels (letter keys, etc.)
var x=osk.keyCodes[key.id];
switch(x)
{
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;
case 219: x=91; break;
case 220: x=92; break;
case 221: x=93; break;
case 222: x=39; break;
default:
if(x < 48 || x > 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<bsk.length; bsn++)
if(bsk[bsn]['sp'] == '1' || bsk[bsn]['sp'] == '2')
{
var oldText=bsk[bsn]['text'];
bsk[bsn]['text']=osk.renameSpecialKey(oldText);
}
}
else btn.subKeys=null;
// Define callbacks to handle key touches: iOS and Android tablets and phones
// TODO: replace inline function calls??
if(!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
}
// Add OSK key labels
var t=util._CreateElement('SPAN'),ts=t.style;
if(key['text'] == null || key['text'] == '')
{
t.innerHTML='\xa0';
if(typeof key['id'] == 'string')
{
if(/^U_[0-9A-F]{4}$/i.test(key['id']))
t.innerHTML=String.fromCharCode(parseInt(key['id'].substr(2),16));
}
}
else t.innerHTML=key['text'];
t.className='kmw-key-text';
// Use special case lookup for modifier keys
if(key['sp'] == '1' || key['sp'] == '2')
{
var tId=((key['text'] == '*Tab*' && n == 1) ? '*TabLeft*' : key['text']);
t.innerHTML=osk.renameSpecialKey(tId);
}
//Override font spec if set for this key in the layout
if('font' in key) ts.fontFamily=key['font'];
if('fontsize' in key) ts.fontSize=key['fontsize'];
// Add text to button and button to placeholder div
btn.appendChild(t);
kDiv.appendChild(btn);
// Prevent user selection of key captions
//t.style.webkitUserSelect='none';
// If a subkey array is defined, add an icon
if(typeof(key['sk']) != 'undefined' && key['sk'] != null)
{
var skIcon=util._CreateElement('DIV');
skIcon.className='kmw-key-popup-icon';
//kDiv.appendChild(skIcon);
btn.appendChild(skIcon);
}
// Add key to row
rDiv.appendChild(kDiv);
rDiv.appendChild(keyTuple.element);
totalPercent += keyTuple.percent;
}
// Add row to layer
gDiv.appendChild(rDiv);
@ -2516,17 +2609,6 @@ if(!window['keyman']['initialized']) {
return lDiv;
}
/**
* Replace default key names by special font codes for modifier keys
*
* @param {string} oldText
* @return {string}
**/
osk.renameSpecialKey = function(oldText) {
// If a 'special key' mapping exists for the text, replace it with its corresponding special OSK character.
return osk.specialCharacters[oldText] ? String.fromCharCode(0XE000 + osk.specialCharacters[oldText]) : oldText;
}
osk.clearPopup = function()
{
// Remove the displayed subkey array, if any, and cancel popup request

View file

@ -26,6 +26,9 @@
# version -- from kmp.inf, js
# minKeymanVersion -- from kmp.inf, kmx, js
# platformSupport -- deduce from whether kmp exists, js exists
# languages -- given the BCP 47 ids, generate the subtag names:
# displayName, languageName - (required)
# scriptName, regionName - if not blank
}
unit MergeKeyboardInfo;
@ -87,6 +90,8 @@ type
procedure AddHelpLink;
procedure AddPlatformSupport;
procedure CheckOrAddVersion;
procedure AddSubtagNames(id: String; o: TJSONObject);
procedure CheckOrMigrateLanguages;
function SaveJsonFile: Boolean;
procedure CheckPackageKeyboardFilenames;
procedure AddIsRTL;
@ -108,7 +113,10 @@ uses
Keyman.System.RegExGroupHelperRSP19902,
BCP47Tag,
JsonUtil,
Keyman.System.KeyboardInfoFile,
Keyman.System.LanguageCodeUtils,
utilfiletypes,
VersionInfo;
@ -178,6 +186,7 @@ begin
CheckPackageKeyboardFilenames;
CheckOrAddID;
CheckOrMigrateLanguages;
AddName;
AddIsRTL;
AddAuthor;
@ -418,6 +427,96 @@ begin
json.AddPair('id', FID);
end;
procedure TMergeKeyboardInfo.AddSubtagNames(id: String; o: TJSONObject);
var
displayName, languageName, scriptName, regionName: String;
v: TJSONValue;
bcp47Tag: TBCP47Tag;
begin
if id = '' then
Exit;
bcp47Tag := TBCP47Tag.Create(id);
try
TLanguageCodeUtils.BCP47Languages.TryGetValue(bcp47Tag.Language, languageName);
TLanguageCodeUtils.BCP47Scripts.TryGetValue(bcp47Tag.Script, scriptName);
TLanguageCodeUtils.BCP47Regions.TryGetValue(bcp47Tag.Region, regionName);
finally
bcp47Tag.Free;
end;
displayName := TLanguageCodeUtils.LanguageName(languageName, scriptName, regionName);
v := o.Values[TKeyboardInfoFile.SDisplayName];
if not Assigned(v) then
o.AddPair(TKeyboardInfoFile.SDisplayName, displayName);
v := o.Values[TKeyboardInfoFile.SLanguageName];
if not Assigned(v) then
o.AddPair(TKeyboardInfoFile.SLanguageName, languageName);
if scriptName <> '' 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
//

View file

@ -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)

View file

@ -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.

View file

@ -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
*/

View file

@ -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;

View file

@ -87,3 +87,4 @@ const
implementation
end.

View file

@ -5,6 +5,10 @@ interface
type
TKeyboardInfoFile = record
const SLanguages = 'languages';
const SDisplayName = 'displayName';
const SLanguageName = 'languageName';
const SScriptName = 'scriptName';
const SRegionName = 'regionName';
end;

View file

@ -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"