@@ -312,6 +312,21 @@ static void invalidateCharacterCoordinates(CFTypeRef viewRef) {
312
312
}
313
313
}
314
314
}
315
+
316
+ static void interpretKeyEvents(CFTypeRef viewRef, CFTypeRef eventRef) {
317
+ @autoreleasepool {
318
+ NSView *view = (__bridge NSView *)viewRef;
319
+ NSEvent *event = (__bridge NSEvent *)eventRef;
320
+ [view interpretKeyEvents:[NSArray arrayWithObject:event]];
321
+ }
322
+ }
323
+
324
+ static int isMiniaturized(CFTypeRef windowRef) {
325
+ @autoreleasepool {
326
+ NSWindow *window = (__bridge NSWindow *)windowRef;
327
+ return window.miniaturized ? 1 : 0;
328
+ }
329
+ }
315
330
*/
316
331
import "C"
317
332
@@ -340,9 +355,20 @@ type window struct {
340
355
cursor pointer.Cursor
341
356
pointerBtns pointer.Buttons
342
357
loop * eventLoop
358
+ lastMods C.NSUInteger
343
359
344
360
scale float32
345
361
config Config
362
+
363
+ keysDown map [key.Name ]struct {}
364
+ // cmdKeys is for storing the current key event while
365
+ // waiting for a doCommandBySelector.
366
+ cmdKeys cmdKeys
367
+ }
368
+
369
+ type cmdKeys struct {
370
+ eventStr string
371
+ eventMods key.Modifiers
346
372
}
347
373
348
374
// launched is closed when applicationDidFinishLaunching is called.
@@ -511,7 +537,7 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
511
537
}
512
538
513
539
func (w * window ) EditorStateChanged (old , new editorState ) {
514
- if old .Selection .Range != new .Selection .Range || old .Snippet != new .Snippet {
540
+ if old .Selection .Range != new .Selection .Range || ! areSnippetsConsistent ( old .Snippet , new .Snippet ) {
515
541
C .discardMarkedText (w .view )
516
542
w .w .SetComposingRegion (key.Range {Start : - 1 , End : - 1 })
517
543
}
@@ -527,7 +553,7 @@ func (w *window) SetInputHint(_ key.InputHint) {}
527
553
func (w * window ) SetAnimating (anim bool ) {
528
554
w .anim = anim
529
555
window := C .windowForView (w .view )
530
- if w .anim && window != 0 {
556
+ if w .anim && window != 0 && C . isMiniaturized ( window ) == 0 {
531
557
w .displayLink .Start ()
532
558
} else {
533
559
w .displayLink .Stop ()
@@ -545,23 +571,92 @@ func (w *window) runOnMain(f func()) {
545
571
}
546
572
547
573
//export gio_onKeys
548
- func gio_onKeys (h C.uintptr_t , cstr C.CFTypeRef , ti C.double , mods C.NSUInteger , keyDown C.bool ) {
574
+ func gio_onKeys (h C.uintptr_t , event C.CFTypeRef , cstr C.CFTypeRef , ti C.double , mods C.NSUInteger , keyDown C.bool ) {
575
+ w := windowFor (h )
576
+ if w .keysDown == nil {
577
+ w .keysDown = make (map [key.Name ]struct {})
578
+ }
549
579
str := nsstringToString (cstr )
550
580
kmods := convertMods (mods )
551
581
ks := key .Release
552
582
if keyDown {
553
583
ks = key .Press
584
+ w .cmdKeys .eventStr = str
585
+ w .cmdKeys .eventMods = kmods
586
+ C .interpretKeyEvents (w .view , event )
554
587
}
555
- w := windowFor (h )
556
588
for _ , k := range str {
557
589
if n , ok := convertKey (k ); ok {
558
- w . ProcessEvent ( key.Event {
590
+ ke := key.Event {
559
591
Name : n ,
560
592
Modifiers : kmods ,
561
593
State : ks ,
594
+ }
595
+ if keyDown {
596
+ w .keysDown [ke .Name ] = struct {}{}
597
+ if _ , isCmd := convertCommandKey (k ); isCmd || kmods .Contain (key .ModCommand ) {
598
+ // doCommandBySelector already processed the event.
599
+ return
600
+ }
601
+ } else {
602
+ if _ , pressed := w .keysDown [n ]; ! pressed {
603
+ continue
604
+ }
605
+ delete (w .keysDown , n )
606
+ }
607
+ w .ProcessEvent (ke )
608
+ }
609
+ }
610
+ }
611
+
612
+ //export gio_onCommandBySelector
613
+ func gio_onCommandBySelector (h C.uintptr_t ) C.bool {
614
+ w := windowFor (h )
615
+ ev := w .cmdKeys
616
+ w .cmdKeys = cmdKeys {}
617
+ handled := false
618
+ for _ , k := range ev .eventStr {
619
+ n , ok := convertCommandKey (k )
620
+ if ! ok && ev .eventMods .Contain (key .ModCommand ) {
621
+ n , ok = convertKey (k )
622
+ }
623
+ if ! ok {
624
+ continue
625
+ }
626
+ ke := key.Event {
627
+ Name : n ,
628
+ Modifiers : ev .eventMods ,
629
+ State : key .Press ,
630
+ }
631
+ handled = w .processEvent (ke ) || handled
632
+ }
633
+ return C .bool (handled )
634
+ }
635
+
636
+ //export gio_onFlagsChanged
637
+ func gio_onFlagsChanged (h C.uintptr_t , curMods C.NSUInteger ) {
638
+ w := windowFor (h )
639
+
640
+ mods := []C.NSUInteger {C .NSControlKeyMask , C .NSAlternateKeyMask , C .NSShiftKeyMask , C .NSCommandKeyMask }
641
+ keys := []key.Name {key .NameCtrl , key .NameAlt , key .NameShift , key .NameCommand }
642
+
643
+ for i , mod := range mods {
644
+ wasPressed := w .lastMods & mod != 0
645
+ isPressed := curMods & mod != 0
646
+
647
+ if wasPressed != isPressed {
648
+ st := key .Release
649
+ if isPressed {
650
+ st = key .Press
651
+ }
652
+ w .ProcessEvent (key.Event {
653
+ Name : keys [i ],
654
+ State : st ,
562
655
})
563
656
}
564
657
}
658
+
659
+ w .lastMods = curMods
565
660
}
566
661
567
662
//export gio_onText
@@ -754,6 +849,15 @@ func gio_substringForProposedRange(h C.uintptr_t, crng C.NSRange, actual C.NSRan
754
849
//export gio_insertText
755
850
func gio_insertText (h C.uintptr_t , cstr C.CFTypeRef , crng C.NSRange ) {
756
851
w := windowFor (h )
852
+ str := nsstringToString (cstr )
853
+ // macOS IME in some cases calls insertText for command keys such as backspace
854
+ // instead of doCommandBySelector.
855
+ for _ , r := range str {
856
+ if _ , ok := convertCommandKey (r ); ok {
857
+ w .w .SetComposingRegion (key.Range {Start : - 1 , End : - 1 })
858
+ return
859
+ }
860
+ }
757
861
state := w .w .EditorState ()
758
862
rng := state .compose
759
863
if rng .Start == - 1 {
@@ -765,7 +869,6 @@ func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
765
869
End : state .RunesIndex (int (crng .location + crng .length )),
766
870
}
767
871
}
768
- str := nsstringToString (cstr )
769
872
w .w .EditorReplace (rng , str )
770
873
w .w .SetComposingRegion (key.Range {Start : - 1 , End : - 1 })
771
874
start := rng .Start
@@ -834,8 +937,13 @@ func (w *window) draw() {
834
937
}
835
938
836
939
func (w * window ) ProcessEvent (e event.Event ) {
837
- w .w .ProcessEvent (e )
940
+ w .processEvent (e )
941
+ }
942
+
943
+ func (w * window ) processEvent (e event.Event ) bool {
944
+ handled := w .w .ProcessEvent (e )
838
945
w .loop .FlushEvents ()
946
+ return handled
839
947
}
840
948
841
949
func (w * window ) Event () event.Event {
@@ -960,10 +1068,10 @@ func osMain() {
960
1068
C .gio_main ()
961
1069
}
962
1070
963
- func convertKey (k rune ) (key.Name , bool ) {
1071
+ func convertCommandKey (k rune ) (key.Name , bool ) {
964
1072
var n key.Name
965
1073
switch k {
966
- case 0x1b :
1074
+ case '\x1b' : // ASCII escape.
967
1075
n = key .NameEscape
968
1076
case C .NSLeftArrowFunctionKey :
969
1077
n = key .NameLeftArrow
@@ -973,22 +1081,36 @@ func convertKey(k rune) (key.Name, bool) {
973
1081
n = key .NameUpArrow
974
1082
case C .NSDownArrowFunctionKey :
975
1083
n = key .NameDownArrow
976
- case 0xd :
1084
+ case '\r' :
977
1085
n = key .NameReturn
978
- case 0x3 :
1086
+ case '\x03' :
979
1087
n = key .NameEnter
980
1088
case C .NSHomeFunctionKey :
981
1089
n = key .NameHome
982
1090
case C .NSEndFunctionKey :
983
1091
n = key .NameEnd
984
- case 0x7f :
1092
+ case '\x7f' , '\b' :
985
1093
n = key .NameDeleteBackward
986
1094
case C .NSDeleteFunctionKey :
987
1095
n = key .NameDeleteForward
1096
+ case '\t' , 0x19 :
1097
+ n = key .NameTab
988
1098
case C .NSPageUpFunctionKey :
989
1099
n = key .NamePageUp
990
1100
case C .NSPageDownFunctionKey :
991
1101
n = key .NamePageDown
1102
+ default :
1103
+ return "" , false
1104
+ }
1105
+ return n , true
1106
+ }
1107
+
1108
+ func convertKey (k rune ) (key.Name , bool ) {
1109
+ if n , ok := convertCommandKey (k ); ok {
1110
+ return n , true
1111
+ }
1112
+ var n key.Name
1113
+ switch k {
992
1114
case C .NSF1FunctionKey :
993
1115
n = key .NameF1
994
1116
case C .NSF2FunctionKey :
@@ -1013,8 +1135,6 @@ func convertKey(k rune) (key.Name, bool) {
1013
1135
n = key .NameF11
1014
1136
case C .NSF12FunctionKey :
1015
1137
n = key .NameF12
1016
- case 0x09 , 0x19 :
1017
- n = key .NameTab
1018
1138
case 0x20 :
1019
1139
n = key .NameSpace
1020
1140
default :
0 commit comments