1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
|
/*
* tkMacOSXKeyboard.c --
*
* Routines to support keyboard events on the Macintosh.
*
* Copyright (c) 1995-1997 Sun Microsystems, Inc.
* Copyright 2001-2009, Apple Inc.
* Copyright (c) 2005-2009 Daniel A. Steffen <das@users.sourceforge.net>
* Copyright (c) 2020 Marc Culler
*
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tkMacOSXPrivate.h"
#include "tkMacOSXEvent.h"
#include "tkMacOSXConstants.h"
#include "tkMacOSXKeysyms.h"
/*
* About keyboards
* ---------------
* Keyboards are complicated. This long comment is an attempt to provide
* enough information about them to make it possible to read and understand
* the code in this file.
*
* Every key on a keyboard is identified by a number between 0 and 127. In
* macOS, pressing or releasing a key on the keyboard generates an NSEvent of
* type KeyDown, KeyUp or FlagsChanged. The 8-bit identifier of the key that
* was involved in this event is provided in the attribute [NSEvent keyCode].
* Apple also refers to this number as a "Virtual KeyCode". In this file, to
* avoid confusion with other uses of the word keycode, we will refer to this
* key identifier as a "virtual keycode", usually the value of a variable named
* "virtual".
*
* Some of the keys on a keyboard, such as the Shift, Option, Command or
* Control keys, are "modifier" keys. The effect of pressing or releasing a
* key depends on three quantities:
* - which key is being pressed or released
* - which modifier keys are being held down at the moment
* - the current keyboard layout
* If the key is a modifier key then the effect of pressing or releasing it is
* only to change the list of which modifier keys are being held down. Apple
* reports this by sending an NSEvent of type FlagsChanged. X11 reports this
* as a KeyPress or KeyRelease event for the modifier key. Note that there may
* be combinations of modifier key states and key presses which have no effect.
*
* In X11 every meaningful effect from a key action is identified by a 16 bit
* value known as a keysym. Every keysym has an associated string name, also
* known as a keysym. The Tk bind command uses the X11 keysym string to
* specify a key event which should invoke a certain action and it provides the
* numeric and symbolic keysyms to the bound proc as %N and %K respectively.
* An X11 XEvent which reports a KeyPress or KeyRelease does not include the
* keysym. Instead it includes a platform-specific numerical value called a
* keycode which is available to the bound procedure as %k. A platform port of
* Tk must provide functions which convert between keycodes and numerical
* keysyms. Conversion between numerical and symbolic keysyms is provided by
* the generic Tk code, although platforms are allowed to provide their own by
* defining the XKeysymToString and XStringToKeysym functions and undefining
* the macro REDO_KEYSYM_LOOKUP. This macOS port uses the conversion provided
* by the generic code.
*
* When the keyboard focus is on a Tk widget which provides text input, there
* are some X11 KeyPress events which cause text to be inserted. We will call
* these "printable" events. The UCS-32 character stored in the keycode field
* of an XKeyEvent depends on more than the three items above. It may also
* depend on the sequence of keypresses that preceded the one being reported by
* the XKeyEvent. For example, on macOS an <Alt-e> event does not cause text
* to be inserted but a following <a> event causes an accented 'a' to be
* inserted. The events in such a composition sequence, other than the final
* one, are known as "dead-key" events.
*
* MacOS packages the information described above in a different way. Every
* meaningful effect from a key action *other than changing the state of
* modifier keys* is identified by a unicode string which is provided as the
* [NSEvent characters] attribute of a KeyDown or KeyUp event. FlagsChanged
* events do not have characters. In principle, the characters attribute could
* be an arbitrary unicode string but in practice it is always a single UTF-16
* character which we usually store in a variable named keychar. While the
* keychar is a legal unicode code point, it does not necessarily represent a
* glyph. MacOS uses unicode code points in the private-use range 0xF700-0xF8FF
* for non-printable events which have no associated ASCII code point. For
* example, pressing the F2 key generates an NSEvent with the character 0xF705,
* the Backspace key produces 0x7F (ASCII del) and the Delete key produces
* 0xF728.
*
* With the exception of modifier keys, it is possible to translate between
* numerical X11 keysyms and macOS keychars; this file constructs Tcl hash
* tables to do this job, using data defined in the file tkMacOSXKeysyms.h.
* The code here adopts the convention that the keychar of any modifier key
* is MOD_KEYCHAR. Keys which do not appear on any Macintosh keyboard, such
* as the Menu key on PC keyboards, are assigned UNKNOWN_KEYCHAR.
*
* The macosx platform-specific scheme for generating a keycode when mapping an
* NSEvent of type KeyUp, KeyDown or FlagsChanged to an XEvent of type KeyPress
* or KeyRelease is as follows:
* keycode = (virtual << 24) | index << 22 | keychar
* where index is a 2-bit quantity whose bits indicate the state of the Option
* and Shift keys.
*
* A few remarks are in order. First, we are using 32 bits for the keycode and
* we are allowing room for up to 22 bits for the keychar. This means that
* there is enough room in the keycode to hold a UTF-32 character, which only
* requires 21 bits. Second, the KeyCode type for the keycode field in an
* XEvent is currently defined as unsigned int, which was modified from the
* unsigned short used in X11 in order to accomodate macOS. Finally, there is
* no obstruction to generating KeyPress events for keys that represent letters
* which do not exist on the current keyboard layout. And different keyboard
* layouts can assign a given letter to different keys. So we need a
* convention for what value to assign to "virtual" when computing the keycode
* for a generated event. The convention used here is as follows: If there is
* a key on the current keyboard which produces the keychar, use the virtual
* keycode of that key. Otherwise set virtual = NO_VIRTUAL.
*/
/*
* See tkMacOSXPrivate.h for macros and structures related to key event processing.
*/
/*
* Hash tables and array used to translate between various key attributes.
*/
static Tcl_HashTable special2keysym; /* Special virtual keycode to keysym */
static Tcl_HashTable keysym2keycode; /* keysym to XEvent keycode */
static Tcl_HashTable keysym2unichar; /* keysym to unichar */
static Tcl_HashTable unichar2keysym; /* unichar to X11 keysym */
static Tcl_HashTable unichar2xvirtual; /* unichar to virtual with index */
static UniChar xvirtual2unichar[512]; /* virtual with index to unichar */
/*
* Flags.
*/
static BOOL initialized = NO;
static BOOL keyboardChanged = YES;
/*
* Prototypes for static functions used in this file.
*/
static void InitHashTables(void);
static void UpdateKeymaps(void);
static int KeyDataToUnicode(UniChar *uniChars, int maxChars,
UInt16 keyaction, UInt32 virtual, UInt32 modifiers,
UInt32 * deadKeyStatePtr);
#pragma mark TKApplication(TKKeyboard)
@implementation TKApplication(TKKeyboard)
- (void) keyboardChanged: (NSNotification *) notification
{
#ifdef TK_MAC_DEBUG_NOTIFICATIONS
TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification);
#else
(void)notification;
#endif
keyboardChanged = YES;
UpdateKeymaps();
}
@end
#pragma mark -
/*
*----------------------------------------------------------------------
*
* InitHashTables --
*
* Creates hash tables used by some of the functions in this file.
*
* Results:
* None.
*
* Side effects:
* Allocates memory & creates some hash tables.
*
*----------------------------------------------------------------------
*/
static void
InitHashTables(void)
{
Tcl_HashEntry *hPtr;
const KeyInfo *kPtr;
const KeysymInfo *ksPtr;
int dummy, index;
Tcl_InitHashTable(&special2keysym, TCL_ONE_WORD_KEYS);
Tcl_InitHashTable(&keysym2keycode, TCL_ONE_WORD_KEYS);
for (kPtr = keyArray; kPtr->virtual != 0; kPtr++) {
MacKeycode macKC;
macKC.v.o_s = 0;
hPtr = Tcl_CreateHashEntry(&special2keysym, INT2PTR(kPtr->virtual),
&dummy);
Tcl_SetHashValue(hPtr, INT2PTR(kPtr->keysym));
hPtr = Tcl_CreateHashEntry(&keysym2keycode, INT2PTR(kPtr->keysym),
&dummy);
macKC.v.virtual = kPtr->virtual;
macKC.v.keychar = kPtr->keychar;
Tcl_SetHashValue(hPtr, INT2PTR(macKC.uint));
/*
* The Carbon framework does not work for finding the unicode character
* of a special key. But that does not depend on the keyboard layout,
* so we can record the information here.
*/
for (index = 3; index >= 0; index--) {
macKC.v.o_s = index;
xvirtual2unichar[macKC.x.xvirtual] = macKC.x.keychar;
}
}
Tcl_InitHashTable(&keysym2unichar, TCL_ONE_WORD_KEYS);
Tcl_InitHashTable(&unichar2keysym, TCL_ONE_WORD_KEYS);
for (ksPtr = keysymTable; ksPtr->keysym != 0; ksPtr++) {
hPtr = Tcl_CreateHashEntry(&keysym2unichar, INT2PTR(ksPtr->keysym),
&dummy);
Tcl_SetHashValue(hPtr, INT2PTR(ksPtr->keycode));
hPtr = Tcl_CreateHashEntry(&unichar2keysym, INT2PTR(ksPtr->keycode),
&dummy);
Tcl_SetHashValue(hPtr, INT2PTR(ksPtr->keysym));
}
UpdateKeymaps();
initialized = YES;
}
/*
*----------------------------------------------------------------------
*
* UpdateKeymaps --
*
* Called when the keyboard changes to update the hash tables that provide
* maps between unicode characters and virtual keycodes with indexes. In
* order for the map from characters to virtual keycodes to be
* well-defined we have to ignore virtual keycodes for keypad keys, since
* each keypad key has the same character as the corresponding key on the
* main keyboard.
*
* Results:
* None.
*
* Side effects:
* Initializes, if necessary, and updates the unichar2xvirtual hash table
* and the xvirtual2unichar array.
*
*----------------------------------------------------------------------
*/
static void
UpdateKeymaps()
{
static Bool keymapInitialized = false;
Tcl_HashEntry *hPtr;
int virtual, index, dummy;
if (!keymapInitialized) {
Tcl_InitHashTable(&unichar2xvirtual, TCL_ONE_WORD_KEYS);
keymapInitialized = true;
} else {
Tcl_DeleteHashTable(&unichar2xvirtual);
Tcl_InitHashTable(&unichar2xvirtual, TCL_ONE_WORD_KEYS);
}
/*
* This loop goes backwards so that a lookup by keychar will provide the
* minimal modifier mask. Simpler combinations will overwrite more complex
* ones when constructing the table.
*/
for (index = 3; index >= 0; index--) {
for (virtual = 0; virtual < 128; virtual++) {
MacKeycode macKC;
macKC.v = (keycode_v) {.virtual = virtual, .o_s = index, .keychar = 0};
int modifiers = INDEX2CARBON(index), result;
UniChar keychar = 0;
result = KeyDataToUnicode(&keychar, 1, kUCKeyActionDown, virtual,
modifiers, NULL);
if (keychar == 0x10) {
/*
* This is a special key, handled in InitHashTables.
*/
continue;
}
macKC.v.keychar = keychar;
if (! ON_KEYPAD(virtual)) {
hPtr = Tcl_CreateHashEntry(&unichar2xvirtual,
INT2PTR(macKC.x.keychar), &dummy);
Tcl_SetHashValue(hPtr, INT2PTR(macKC.x.xvirtual));
}
xvirtual2unichar[macKC.x.xvirtual] = macKC.x.keychar;
}
}
}
/*
*----------------------------------------------------------------------
*
* KeyDataToUnicode --
*
* Given MacOS key event data this function generates the keychar. It
* does this by using OS resources from the Carbon framework. Note that
* the Carbon functions used here are not aware of the keychars in the
* private-use range which macOS now uses for special keys. For those
* keys this function returns 0x10 (ASCII dle).
*
* The parameter deadKeyStatePtr can be NULL, if no deadkey handling is
* needed (which is always the case here).
*
* This function is called in XKeycodeToKeysym and UpdateKeymaps.
*
* Results:
* The number of characters generated if any, 0 if we are waiting for
* another byte of a dead-key sequence.
*
* Side Effects:
* Fills in the uniChars array with a Unicode string.
*
*----------------------------------------------------------------------
*/
static int
KeyDataToUnicode(
UniChar *uniChars,
int maxChars,
UInt16 keyaction,
UInt32 virtual,
UInt32 modifiers,
UInt32 *deadKeyStatePtr)
{
static const void *layoutData = NULL;
static UInt32 keyboardType = 0;
UniCharCount actuallength = 0;
if (keyboardChanged) {
TISInputSourceRef currentKeyboardLayout =
TISCopyCurrentKeyboardLayoutInputSource();
if (currentKeyboardLayout) {
CFDataRef keyLayoutData = (CFDataRef) TISGetInputSourceProperty(
currentKeyboardLayout, kTISPropertyUnicodeKeyLayoutData);
if (keyLayoutData) {
layoutData = CFDataGetBytePtr(keyLayoutData);
keyboardType = LMGetKbdType();
}
CFRelease(currentKeyboardLayout);
}
keyboardChanged = 0;
}
if (layoutData) {
OptionBits options = 0;
UInt32 dummyState;
OSStatus err;
virtual &= 0xFF;
modifiers = (modifiers >> 8) & 0xFF;
if (!deadKeyStatePtr) {
options = kUCKeyTranslateNoDeadKeysMask;
dummyState = 0;
deadKeyStatePtr = &dummyState;
}
err = ChkErr(UCKeyTranslate, layoutData, virtual, keyaction, modifiers,
keyboardType, options, deadKeyStatePtr, maxChars,
&actuallength, uniChars);
if (!actuallength && *deadKeyStatePtr) {
/*
* We are waiting for another key.
*/
return 0;
}
*deadKeyStatePtr = 0;
if (err != noErr) {
actuallength = 0;
}
}
return actuallength;
}
/*
*----------------------------------------------------------------------
*
* XKeycodeToKeysym --
*
* This is a stub function which translates from the keycode used in an
* XEvent to a numerical keysym. On macOS, the display parameter is
* ignored and only the the virtual keycode stored in the .virtual bitfield
* of a MacKeycode.v.
*
* Results:
* Returns the corresponding numerical keysym, or NoSymbol if the keysym
* cannot be found.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
KeySym
XkbKeycodeToKeysym(
TCL_UNUSED(Display *),
unsigned int keycode,
TCL_UNUSED(int),
int index)
{
Tcl_HashEntry *hPtr;
MacKeycode macKC;
int modifiers, result;
UniChar keychar = 0;
if (!initialized) {
InitHashTables();
}
macKC.uint = keycode;
macKC.v.o_s = index;
/*
* First check if the virtual keycode corresponds to a special key, such as
* an Fn function key or Tab, Backspace, Home, End, etc.
*/
hPtr = Tcl_FindHashEntry(&special2keysym, INT2PTR(macKC.v.virtual));
if (hPtr != NULL) {
return (KeySym) Tcl_GetHashValue(hPtr);
}
/*
* If the virtual value in this keycode does not correspond to an actual
* key in the current keyboard layout, try using its keychar to look up a
* keysym.
*/
if (macKC.v.virtual > 127) {
hPtr = Tcl_FindHashEntry(&unichar2keysym, INT2PTR(macKC.v.keychar));
if (hPtr != NULL) {
return (KeySym) Tcl_GetHashValue(hPtr);
}
}
/*
* If the virtual keycode does belong to a key, use the virtual and the
* Option-Shift from index to look up a keychar by using the Carbon
* Framework; then translate the keychar to a keysym using the
* unicode2keysym hash table.
*/
modifiers = INDEX2CARBON(macKC.v.o_s);
result = KeyDataToUnicode(&keychar, 1, kUCKeyActionDown, macKC.v.virtual,
modifiers, NULL);
if (result) {
hPtr = Tcl_FindHashEntry(&unichar2keysym, INT2PTR(keychar));
if (hPtr != NULL) {
return (KeySym) Tcl_GetHashValue(hPtr);
}
}
return NoSymbol;
}
KeySym
XKeycodeToKeysym(
TCL_UNUSED(Display *),
KeyCode keycode,
int index)
{
return XkbKeycodeToKeysym(NULL, keycode, 0, index);
}
/*
*----------------------------------------------------------------------
*
* TkpGetString --
*
* This is a stub function which retrieves the string stored in the
* transchars field of an XEvent and converts it to a Tcl_DString.
*
* Results:
* Returns a pointer to the string value of the DString.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
const char *
TkpGetString(
TCL_UNUSED(TkWindow *), /* Window where event occurred: Needed to get
* input context. */
XEvent *eventPtr, /* X keyboard event. */
Tcl_DString *dsPtr) /* Uninitialized or empty string to hold
* result. */
{
MacKeycode macKC;
char utfChars[8];
int length = 0;
macKC.uint = eventPtr->xkey.keycode;
if (IS_PRINTABLE(macKC.v.keychar)) {
length = TkUniCharToUtf(macKC.v.keychar, utfChars);
}
utfChars[length] = 0;
Tcl_DStringInit(dsPtr);
return Tcl_DStringAppend(dsPtr, utfChars, length);
}
/*
*----------------------------------------------------------------------
*
* XGetModifierMapping --
*
* X11 stub function to get the keycodes used as modifiers. This
* is never called by the macOS port.
*
* Results:
* Returns a newly allocated modifier map.
*
* Side effects:
* Allocates a new modifier map data structure.
*
*----------------------------------------------------------------------
*/
XModifierKeymap *
XGetModifierMapping(
TCL_UNUSED(Display *))
{
XModifierKeymap *modmap;
modmap = (XModifierKeymap *)ckalloc(sizeof(XModifierKeymap));
modmap->max_keypermod = 0;
modmap->modifiermap = NULL;
return modmap;
}
/*
*----------------------------------------------------------------------
*
* XFreeModifiermap --
*
* Deallocates a modifier map that was created by XGetModifierMapping.
* This is also never called by the macOS port.
*
* Results:
* None.
*
* Side effects:
* Frees the datastructure referenced by modmap.
*
*----------------------------------------------------------------------
*/
int
XFreeModifiermap(
XModifierKeymap *modmap)
{
if (modmap->modifiermap != NULL) {
ckfree(modmap->modifiermap);
}
ckfree(modmap);
return Success;
}
/*
*----------------------------------------------------------------------
*
* XKeysymToString, XStringToKeysym --
*
* These X11 stub functions map keysyms to strings & strings to keysyms.
* A platform can do its own conversion by defining these and undefining
* REDO_KEYSYM_LOOKUP. The macOS port defines REDO_KEYSYM_LOOKUP so these
* are never called and Tk does the conversion for us.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
char *
XKeysymToString(
TCL_UNUSED(KeySym))
{
return NULL;
}
KeySym
XStringToKeysym(
TCL_UNUSED(const char *))
{
return NoSymbol;
}
/*
*----------------------------------------------------------------------
*
* XKeysymToKeycode --
*
* This is a stub function which converts a numerical keysym to the
* platform-specific keycode used in a KeyPress or KeyRelease XEvent.
* For macOS the keycode is an unsigned int with bitfields described
* in the definition of the MacKeycode type.
*
* Results:
*
* A macOS KeyCode. See the description of keycodes at the top of this
* file and the definition of the MacKeycode type in tkMacOSXPrivate.h.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
KeyCode
XKeysymToKeycode(
TCL_UNUSED(Display *),
KeySym keysym)
{
Tcl_HashEntry *hPtr;
MacKeycode macKC;
if (!initialized) {
InitHashTables();
}
/*
* First check for a special key.
*/
hPtr = Tcl_FindHashEntry(&keysym2keycode, INT2PTR(keysym));
if (hPtr != NULL) {
return (KeyCode) Tcl_GetHashValue(hPtr);
}
/*
* Initialize the keycode as if the keysym cannot be converted to anything
* else.
*/
macKC.v.virtual = NO_VIRTUAL;
macKC.v.o_s = 0;
macKC.v.keychar = 0;
/*
* If the keysym is recognized fill in the keychar. Also fill in the
* xvirtual field if the key exists on the current keyboard.
*/
hPtr = (Tcl_HashEntry *) Tcl_FindHashEntry(&keysym2unichar,
INT2PTR(keysym));
if (hPtr != NULL) {
unsigned long data = (unsigned long) Tcl_GetHashValue(hPtr);
macKC.x.keychar = (unsigned int) data;
hPtr = Tcl_FindHashEntry(&unichar2xvirtual, INT2PTR(macKC.x.keychar));
if (hPtr != NULL) {
unsigned long data = (unsigned long) Tcl_GetHashValue(hPtr);
macKC.x.xvirtual = (unsigned int) data;
}
}
return (KeyCode) macKC.uint;
}
/*
*----------------------------------------------------------------------
*
* TkpSetKeycodeAndState --
*
* This function accepts a keysym and an XEvent and sets some fields of
* the XEvent. It is used by the event generate command.
*
* Results:
* None
*
* Side effects:
*
* Modifies the XEvent. Sets the xkey.keycode to a keycode value formatted
* by XKeysymToKeycode and updates the shift and option flags in
* xkey.state if either of those modifiers is required to generate the
* keysym.
*
*----------------------------------------------------------------------
*/
void
TkpSetKeycodeAndState(
TCL_UNUSED(Tk_Window),
KeySym keysym,
XEvent *eventPtr)
{
if (keysym == NoSymbol) {
eventPtr->xkey.keycode = 0;
} else {
int eventIndex = STATE2INDEX(eventPtr->xkey.state);
MacKeycode macKC;
macKC.uint = XKeysymToKeycode(NULL, keysym);
/*
* We have a virtual keycode and a minimal choice for Shift and Option
* modifiers which generates the keychar that corresponds to the
* specified keysym. But we might not have the correct keychar yet,
* because the xEvent may have specified modifiers beyond our minimal
* set. For example, the events described by <Oslash>, <Shift-oslash>,
* <Shift-Option-O> and <Shift-Option-o> should all produce the same
* uppercase Danish O. So we may need to add the extra modifiers and
* do another lookup for the keychar. We don't want to do this for
* special keys, however.
*/
if (macKC.v.o_s != eventIndex) {
macKC.v.o_s |= eventIndex;
}
if (macKC.v.keychar < 0xF700) {
UniChar keychar = macKC.v.keychar;
NSString *str, *lower, *upper;
if (macKC.v.virtual != NO_VIRTUAL) {
macKC.x.keychar = xvirtual2unichar[macKC.x.xvirtual];
} else {
str = [[NSString alloc] initWithCharacters:&keychar length:1];
lower = [str lowercaseString];
upper = [str uppercaseString];
if (![str isEqual: lower]) {
macKC.v.o_s |= INDEX_SHIFT;
}
if (macKC.v.o_s & INDEX_SHIFT) {
macKC.v.keychar = [upper characterAtIndex:0];
}
}
}
eventPtr->xkey.keycode = macKC.uint;
eventPtr->xkey.state |= INDEX2STATE(macKC.v.o_s);
}
}
/*
*----------------------------------------------------------------------
*
* TkpGetKeySym --
*
* This is a stub function called in tkBind.c. Given a KeyPress or
* KeyRelease XEvent, it maps the keycode in the event to a numerical
* keysym.
*
* Results:
* The return value is the keysym corresponding to eventPtr, or NoSymbol
* if no matching keysym could be found.
*
* Side effects:
* In the first call for a given display, calls TkpInitKeymapInfo.
*
*
*----------------------------------------------------------------------
*/
KeySym
TkpGetKeySym(
TkDisplay *dispPtr, /* Display in which to map keycode. */
XEvent *eventPtr) /* Description of X event. */
{
KeySym sym;
int index;
MacKeycode macKC;
macKC.uint = eventPtr->xkey.keycode;
/*
* Refresh the mapping information if it's stale.
*/
if (dispPtr->bindInfoStale) {
TkpInitKeymapInfo(dispPtr);
}
/*
* Modifier key events have a special mac keycode (see tkProcessKeyEvent).
*/
if (macKC.v.keychar == MOD_KEYCHAR) {
switch (macKC.v.virtual) {
case 54:
return XK_Meta_R;
case 55:
return XK_Meta_L;
case 56:
return XK_Shift_L;
case 57:
return XK_Caps_Lock;
case 58:
return XK_Alt_L;
case 59:
return XK_Control_L;
case 60:
return XK_Shift_R;
case 61:
return XK_Alt_R;
case 62:
return XK_Control_R;
case 63:
return XK_Super_L;
default:
return NoSymbol;
}
}
/*
* Figure out which of the four slots in the keymap vector to use for this
* key. Refer to Xlib documentation for more info on how this computation
* works.
*/
index = STATE2INDEX(eventPtr->xkey.state);
if (eventPtr->xkey.state & LockMask) {
index |= INDEX_SHIFT;
}
/*
* First do the straightforward lookup.
*/
sym = XkbKeycodeToKeysym(dispPtr->display, macKC.uint, 0, index);
/*
* Special handling: If the key was shifted because of Lock, which is only
* caps lock on macOS, not shift lock, and if the shifted keysym isn't
* upper-case alphabetic, then switch back to the unshifted keysym.
*/
if ((index & INDEX_SHIFT) && !(eventPtr->xkey.state & ShiftMask)) {
if ((sym == NoSymbol) || !Tcl_UniCharIsUpper(sym)) {
sym = XkbKeycodeToKeysym(dispPtr->display, macKC.uint, 0,
index & ~INDEX_SHIFT);
}
}
/*
* Another bit of special handling: If this is a shifted key and there is
* no keysym defined, then use the keysym for the unshifted key.
*/
if ((index & INDEX_SHIFT) && (sym == NoSymbol)) {
sym = XkbKeycodeToKeysym(dispPtr->display, macKC.uint, 0,
index & ~INDEX_SHIFT);
}
return sym;
}
/*
*--------------------------------------------------------------
*
* TkpInitKeymapInfo --
*
* This procedure initializes fields in the display that pertain
* to modifier keys.
*
* Results:
* None.
*
* Side effects:
* Modifier key information in dispPtr is initialized.
*
*--------------------------------------------------------------
*/
void
TkpInitKeymapInfo(
TkDisplay *dispPtr) /* Display for which to recompute keymap
* information. */
{
dispPtr->bindInfoStale = 0;
/*
* On macOS the caps lock key is always interpreted to mean that alphabetic
* keys become uppercase but other keys do not get shifted. (X11 allows
* a configuration option which makes the caps lock equivalent to holding
* down the shift key.)
* There is no offical "Mode_switch" key.
*/
dispPtr->lockUsage = LU_CAPS;
/* This field is no longer used by tkBind.c */
dispPtr->modeModMask = 0;
/* The Alt and Meta keys are interchanged on Macintosh keyboards compared
* to PC keyboards. These fields could be set to make the Alt key on a PC
* keyboard behave likd an Alt key. That would also require interchanging
* Mod1Mask and Mod2Mask in tkMacOSXKeyEvent.c.
*/
dispPtr->altModMask = 0;
dispPtr->metaModMask = 0;
/*
* The modKeyCodes table lists the keycodes that appear in KeyPress or
* KeyRelease XEvents for modifier keys. In tkBind.c this table is
* searched to determine whether an XEvent corresponds to a modifier key.
*/
if (dispPtr->modKeyCodes != NULL) {
ckfree(dispPtr->modKeyCodes);
}
dispPtr->numModKeyCodes = NUM_MOD_KEYCODES;
dispPtr->modKeyCodes = (KeyCode *)ckalloc(NUM_MOD_KEYCODES * sizeof(KeyCode));
for (int i = 0; i < NUM_MOD_KEYCODES; i++) {
dispPtr->modKeyCodes[i] = XKeysymToKeycode(NULL, modKeyArray[i]);
}
}
/*
*--------------------------------------------------------------
*
* TkMacOSXAddVirtual --
*
* This procedure is an internal utility which accepts an unsigned int
* that has been partially filled as a MacKeycode, having the Option and
* Shift state set in the o_s field and the keychar field set but with the
* virtual keycode blank. It looks up the virtual keycode for the keychar
* (possibly NO_VIRTUAL) and returns an unsigned int which is a complete
* MacKeycode with the looked up virtual keycode added. This is used when
* creating XEvents for the unicode characters which are generated by the
* NSTextInputClient.
*
* Results:
* An unsigned int which is a complete MacKeycode, including a virtual
* keycode which matches the Option-Shift state and keychar.
*
* Side effects:
* None
*
*--------------------------------------------------------------
*/
unsigned
TkMacOSXAddVirtual(
unsigned int keycode)
{
MacKeycode macKC;
Tcl_HashEntry *hPtr;
macKC.uint = keycode;
if (!initialized) {
InitHashTables();
}
hPtr = (Tcl_HashEntry *) Tcl_FindHashEntry(&unichar2xvirtual,
INT2PTR(macKC.v.keychar));
if (hPtr != NULL) {
unsigned long data = (unsigned long) Tcl_GetHashValue(hPtr);
macKC.x.xvirtual = (unsigned int) data;
} else {
macKC.v.virtual = NO_VIRTUAL;
}
return macKC.uint;
}
/*
* Local Variables:
* mode: objc
* c-basic-offset: 4
* fill-column: 79
* coding: utf-8
* End:
*/
|