From de4ba64cf58948d044322d0a9743ef5cc46ef5c7 Mon Sep 17 00:00:00 2001 From: David Crawshaw Date: Tue, 30 Jun 2015 14:24:27 -0400 Subject: [PATCH] app, event/key: keyboard events First cut of events from physical keyboards. Simple darwin/amd64 implementation. Change-Id: I6e9d0a253387c841864ca9845ee729ea4f7573c7 Reviewed-on: https://go-review.googlesource.com/11815 Reviewed-by: Nigel Tao --- app/darwin_amd64.go | 322 ++++++++++++++++++++++++++++++++++++++++++++ app/darwin_amd64.m | 52 ++++++- event/key/key.go | 228 +++++++++++++++++++++++++++++++ 3 files changed, 601 insertions(+), 1 deletion(-) create mode 100644 event/key/key.go diff --git a/app/darwin_amd64.go b/app/darwin_amd64.go index a18219d..0d0e6ef 100644 --- a/app/darwin_amd64.go +++ b/app/darwin_amd64.go @@ -13,6 +13,7 @@ package app /* #cgo CFLAGS: -x objective-c #cgo LDFLAGS: -framework Cocoa -framework OpenGL -framework QuartzCore +#import // for HIToolbox/Events.h #import #include @@ -28,6 +29,7 @@ import ( "sync" "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/key" "golang.org/x/mobile/event/lifecycle" "golang.org/x/mobile/event/paint" "golang.org/x/mobile/event/touch" @@ -150,6 +152,55 @@ func eventMouseEnd(x, y float32) { sendTouch(touch.TypeEnd, x, y) } //export lifecycleDead func lifecycleDead() { sendLifecycle(lifecycle.StageDead) } +//export eventKey +func eventKey(runeVal int32, direction uint8, code uint16, flags uint32) { + var modifiers key.Modifiers + for _, mod := range mods { + if flags&mod.flags == mod.flags { + modifiers |= mod.mod + } + } + + eventsIn <- key.Event{ + Rune: convRune(rune(runeVal)), + Code: convVirtualKeyCode(code), + Modifiers: modifiers, + Direction: key.Direction(direction), + } +} + +//export eventFlags +func eventFlags(flags uint32) { + for _, mod := range mods { + if flags&mod.flags == mod.flags && lastFlags&mod.flags != mod.flags { + eventKey(-1, uint8(key.DirPress), mod.code, flags) + } + if lastFlags&mod.flags == mod.flags && flags&mod.flags != mod.flags { + eventKey(-1, uint8(key.DirRelease), mod.code, flags) + } + } + lastFlags = flags +} + +var lastFlags uint32 + +var mods = [...]struct { + flags uint32 + code uint16 + mod key.Modifiers +}{ + // Left and right variants of modifier keys have their own masks, + // but they are not documented. These were determined empirically. + {1<<17 | 0x102, C.kVK_Shift, key.ModShift}, + {1<<17 | 0x104, C.kVK_RightShift, key.ModShift}, + {1<<18 | 0x101, C.kVK_Control, key.ModControl}, + // TODO key.ControlRight + {1<<19 | 0x120, C.kVK_Option, key.ModAlt}, + {1<<19 | 0x140, C.kVK_RightOption, key.ModAlt}, + {1<<20 | 0x108, C.kVK_Command, key.ModMeta}, + {1<<20 | 0x110, C.kVK_Command, key.ModMeta}, // TODO: missing kVK_RightCommand +} + //export lifecycleAlive func lifecycleAlive() { sendLifecycle(lifecycle.StageAlive) } @@ -158,3 +209,274 @@ func lifecycleVisible() { sendLifecycle(lifecycle.StageVisible) } //export lifecycleFocused func lifecycleFocused() { sendLifecycle(lifecycle.StageFocused) } + +// convRune marks the Carbon/Cocoa private-range unicode rune representing +// a non-unicode key event to -1, used for Rune in the key package. +// +// http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT +func convRune(r rune) rune { + if '\uE000' <= r && r <= '\uF8FF' { + return -1 + } + return r +} + +// convVirtualKeyCode converts a Carbon/Cocoa virtual key code number +// into the standard keycodes used by the key package. +// +// To get a sense of the key map, see the diagram on +// http://boredzo.org/blog/archives/2007-05-22/virtual-key-codes +func convVirtualKeyCode(vkcode uint16) uint32 { + switch vkcode { + case C.kVK_ANSI_A: + return key.CodeA + case C.kVK_ANSI_B: + return key.CodeB + case C.kVK_ANSI_C: + return key.CodeC + case C.kVK_ANSI_D: + return key.CodeD + case C.kVK_ANSI_E: + return key.CodeE + case C.kVK_ANSI_F: + return key.CodeF + case C.kVK_ANSI_G: + return key.CodeG + case C.kVK_ANSI_H: + return key.CodeH + case C.kVK_ANSI_I: + return key.CodeI + case C.kVK_ANSI_J: + return key.CodeJ + case C.kVK_ANSI_K: + return key.CodeK + case C.kVK_ANSI_L: + return key.CodeL + case C.kVK_ANSI_M: + return key.CodeM + case C.kVK_ANSI_N: + return key.CodeN + case C.kVK_ANSI_O: + return key.CodeO + case C.kVK_ANSI_P: + return key.CodeP + case C.kVK_ANSI_Q: + return key.CodeQ + case C.kVK_ANSI_R: + return key.CodeR + case C.kVK_ANSI_S: + return key.CodeS + case C.kVK_ANSI_T: + return key.CodeT + case C.kVK_ANSI_U: + return key.CodeU + case C.kVK_ANSI_V: + return key.CodeV + case C.kVK_ANSI_W: + return key.CodeW + case C.kVK_ANSI_X: + return key.CodeX + case C.kVK_ANSI_Y: + return key.CodeY + case C.kVK_ANSI_Z: + return key.CodeZ + case C.kVK_ANSI_1: + return key.Code1 + case C.kVK_ANSI_2: + return key.Code2 + case C.kVK_ANSI_3: + return key.Code3 + case C.kVK_ANSI_4: + return key.Code4 + case C.kVK_ANSI_5: + return key.Code5 + case C.kVK_ANSI_6: + return key.Code6 + case C.kVK_ANSI_7: + return key.Code7 + case C.kVK_ANSI_8: + return key.Code8 + case C.kVK_ANSI_9: + return key.Code9 + case C.kVK_ANSI_0: + return key.Code0 + // TODO: move the rest of these codes to constants in key.go + // if we are happy with them. + case C.kVK_Return: + return key.CodeReturn + case C.kVK_Escape: + return key.CodeEscape + case C.kVK_Delete: + return key.CodeBackspace + case C.kVK_Tab: + return key.CodeTab + case C.kVK_Space: + return 44 + case C.kVK_ANSI_Minus: + return 45 + case C.kVK_ANSI_Equal: + return 46 + case C.kVK_ANSI_LeftBracket: + return 47 + case C.kVK_ANSI_RightBracket: + return 48 + case C.kVK_ANSI_Backslash: + return 49 + // 50: Keyboard Non-US "#" and ~ + case C.kVK_ANSI_Semicolon: + return 51 + case C.kVK_ANSI_Quote: + return 52 + case C.kVK_ANSI_Grave: + return 53 + case C.kVK_ANSI_Comma: + return 54 + case C.kVK_ANSI_Period: + return 55 + case C.kVK_ANSI_Slash: + return 56 + case C.kVK_CapsLock: + return 57 + case C.kVK_F1: + return key.CodeF1 + case C.kVK_F2: + return key.CodeF2 + case C.kVK_F3: + return key.CodeF3 + case C.kVK_F4: + return key.CodeF4 + case C.kVK_F5: + return key.CodeF5 + case C.kVK_F6: + return key.CodeF6 + case C.kVK_F7: + return key.CodeF7 + case C.kVK_F8: + return key.CodeF8 + case C.kVK_F9: + return key.CodeF9 + case C.kVK_F10: + return key.CodeF10 + case C.kVK_F11: + return key.CodeF11 + case C.kVK_F12: + return key.CodeF12 + // 70: PrintScreen + // 71: Scroll Lock + // 72: Pause + // 73: Insert + case C.kVK_Home: + return 74 + case C.kVK_PageUp: + return key.CodePageUp + case C.kVK_ForwardDelete: + return 76 + case C.kVK_End: + return 77 + case C.kVK_PageDown: + return key.CodePageDown + case C.kVK_RightArrow: + return key.CodeRightArrow + case C.kVK_LeftArrow: + return key.CodeLeftArrow + case C.kVK_DownArrow: + return key.CodeDownArrow + case C.kVK_UpArrow: + return key.CodeUpArrow + case C.kVK_ANSI_KeypadClear: + return key.CodeKeypadNumLockAndClear + case C.kVK_ANSI_KeypadDivide: + return key.CodeKeypadSlash + case C.kVK_ANSI_KeypadMultiply: + return key.CodeKeypadAsterisk + case C.kVK_ANSI_KeypadMinus: + return key.CodeKeypadMinus + case C.kVK_ANSI_KeypadPlus: + return key.CodeKeypadPlus + case C.kVK_ANSI_KeypadEnter: + return key.CodeKeypadEnter + case C.kVK_ANSI_Keypad1: + return key.CodeKeypad1 + case C.kVK_ANSI_Keypad2: + return key.CodeKeypad2 + case C.kVK_ANSI_Keypad3: + return key.CodeKeypad3 + case C.kVK_ANSI_Keypad4: + return key.CodeKeypad4 + case C.kVK_ANSI_Keypad5: + return key.CodeKeypad5 + case C.kVK_ANSI_Keypad6: + return key.CodeKeypad6 + case C.kVK_ANSI_Keypad7: + return key.CodeKeypad7 + case C.kVK_ANSI_Keypad8: + return key.CodeKeypad8 + case C.kVK_ANSI_Keypad9: + return key.CodeKeypad9 + case C.kVK_ANSI_Keypad0: + return key.CodeKeypad0 + case C.kVK_ANSI_KeypadDecimal: + return key.CodeKeypadFullStop + case C.kVK_ANSI_KeypadEquals: + return key.CodeKeypadEqualSign + case C.kVK_F13: + return key.CodeF13 + case C.kVK_F14: + return key.CodeF14 + case C.kVK_F15: + return key.CodeF15 + case C.kVK_F16: + return key.CodeF16 + case C.kVK_F17: + return key.CodeF17 + case C.kVK_F18: + return key.CodeF18 + case C.kVK_F19: + return key.CodeF19 + case C.kVK_F20: + return key.CodeF20 + // 116: Keyboard Execute + case C.kVK_Help: + return key.CodeHelp + // 118: Keyboard Menu + // 119: Keyboard Select + // 120: Keyboard Stop + // 121: Keyboard Again + // 122: Keyboard Undo + // 123: Keyboard Cut + // 124: Keyboard Copy + // 125: Keyboard Paste + // 126: Keyboard Find + case C.kVK_Mute: + return key.CodeMute + case C.kVK_VolumeUp: + return key.CodeVolumeUp + case C.kVK_VolumeDown: + return key.CodeVolumeDown + // 130: Keyboard Locking Caps Lock + // 131: Keyboard Locking Num Lock + // 132: Keyboard Locking Scroll Lock + // 133: Keyboard Comma + // 134: Keyboard Equal Sign + // ...: Bunch of stuff + case C.kVK_Control: + return key.CodeLeftControl + case C.kVK_Shift: + return key.CodeLeftShift + case C.kVK_Option: + return key.CodeLeftAlt + case C.kVK_Command: + return key.CodeLeftMeta + case C.kVK_RightControl: + return key.CodeRightControl + case C.kVK_RightShift: + return key.CodeRightShift + case C.kVK_RightOption: + return key.CodeRightAlt + // TODO key.CodeRightMeta + case C.kVK_Function: + return 3 // TODO + default: + return 3 // Keyboard ErrorUndefined + } +} diff --git a/app/darwin_amd64.m b/app/darwin_amd64.m index c8e3731..cd9e6e2 100644 --- a/app/darwin_amd64.m +++ b/app/darwin_amd64.m @@ -159,7 +159,54 @@ uint64 threadID() { } @end -void runApp(void) { +@interface MobileResponder : NSResponder +{ +} +@end + +@implementation MobileResponder +- (void)keyDown:(NSEvent *)theEvent { + [self key:theEvent]; +} +- (void)keyUp:(NSEvent *)theEvent { + [self key:theEvent]; +} +- (void)key:(NSEvent *)theEvent { + NSRange range = [theEvent.characters rangeOfComposedCharacterSequenceAtIndex:0]; + + uint8_t buf[4] = {0, 0, 0, 0}; + if (![theEvent.characters getBytes:buf + maxLength:4 + usedLength:nil + encoding:NSUTF32LittleEndianStringEncoding + options:NSStringEncodingConversionAllowLossy + range:range + remainingRange:nil]) { + NSLog(@"failed to read key event %@", theEvent); + return; + } + + uint32_t rune = (uint32_t)buf[0]<<0 | (uint32_t)buf[1]<<8 | (uint32_t)buf[2]<<16 | (uint32_t)buf[3]<<24; + + uint8_t direction; + if (theEvent.ARepeat) { + direction = 0; + } else if (theEvent.type == NSKeyDown) { + direction = 1; + } else { + direction = 2; + } + eventKey((int32_t)rune, direction, theEvent.keyCode, theEvent.modifierFlags); +} + +- (void)flagsChanged:(NSEvent *)theEvent { + eventFlags(theEvent.modifierFlags); +} +@end + + +void +runApp(void) { [NSAutoreleasePool new]; [NSApplication sharedApplication]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; @@ -211,6 +258,9 @@ void runApp(void) { [window setContentView:view]; [window setDelegate:view]; [NSApp setDelegate:view]; + + window.nextResponder = [[[MobileResponder alloc] init] autorelease]; + [NSApp run]; } diff --git a/event/key/key.go b/event/key/key.go new file mode 100644 index 0000000..bfb792b --- /dev/null +++ b/event/key/key.go @@ -0,0 +1,228 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package key defines an event for physical keyboard keys. +// +// On-screen software keyboards do not send key events. +// +// See the golang.org/x/mobile/app package for details on the event model. +package key + +import ( + "fmt" + "strings" +) + +// Event is a key event. +type Event struct { + // Rune is the meaning of the key event as determined by the + // operating system. The mapping is determined by system-dependent + // current layout, modifiers, lock-states, etc. + // + // If non-negative, it is a Unicode codepoint: pressing the 'a' key + // generates different Runes 'a' or 'A' (but the same Code) depending on + // the state of the shift key. + // + // If -1, the key does not generate a Unicode codepoint. To distinguish + // them, look at Code. + Rune rune + + // Code is the identity of the physical key relative to a notional + // "standard" keyboard, independent of current layout, modifiers, + // lock-states, etc + // + // For standard key codes, its value matches USB HID key codes. + // Compare its value to uint32-typed constants in this package, such + // as CodeLeftShift and CodeEscape. + // TODO(crawshaw): define "type Code uint32" + // + // Pressing the regular '2' key and number-pad '2' key (with Num-Lock) + // generate different Codes (but the same Rune). + Code uint32 + + // Modifiers is a bitmask representing a set of modifier keys: ModShift, + // ModAlt, etc. + Modifiers Modifiers + + // Direction is the direction of the key event: DirPress, DirRelease, + // or DirNone (for key repeats). + Direction Direction + + // TODO: add a Device ID, for multiple input devices? + // TODO: add a time.Time? +} + +// Direction is the direction of the key event. +type Direction uint8 + +const ( + DirNone Direction = 0 + DirPress Direction = 1 + DirRelease Direction = 2 +) + +// Modifiers is a bitmask representing a set of modifier keys. +type Modifiers uint32 + +const ( + ModShift Modifiers = 1 << 0 + ModControl Modifiers = 1 << 1 + ModAlt Modifiers = 1 << 2 + ModMeta Modifiers = 1 << 3 // called "Command" on OS X +) + +// Physical key codes. +// +// For standard key codes, its value matches USB HID key codes. +// TODO: add missing codes. +const ( + CodeA = 4 + CodeB = 5 + CodeC = 6 + CodeD = 7 + CodeE = 8 + CodeF = 9 + CodeG = 10 + CodeH = 11 + CodeI = 12 + CodeJ = 13 + CodeK = 14 + CodeL = 15 + CodeM = 16 + CodeN = 17 + CodeO = 18 + CodeP = 19 + CodeQ = 20 + CodeR = 21 + CodeS = 22 + CodeT = 23 + CodeU = 24 + CodeV = 25 + CodeW = 26 + CodeX = 27 + CodeY = 28 + CodeZ = 29 + + Code1 = 30 + Code2 = 31 + Code3 = 32 + Code4 = 33 + Code5 = 34 + Code6 = 35 + Code7 = 36 + Code8 = 37 + Code9 = 38 + Code0 = 39 + + CodeReturn = 40 + CodeEscape = 41 + CodeBackspace = 42 + CodeTab = 43 + + CodeF1 = 58 + CodeF2 = 59 + CodeF3 = 60 + CodeF4 = 61 + CodeF5 = 62 + CodeF6 = 63 + CodeF7 = 64 + CodeF8 = 65 + CodeF9 = 66 + CodeF10 = 67 + CodeF11 = 68 + CodeF12 = 69 + + CodePageUp = 75 + CodePageDown = 78 + + CodeRightArrow = 79 + CodeLeftArrow = 80 + CodeDownArrow = 81 + CodeUpArrow = 82 + + CodeKeypadNumLockAndClear = 83 + CodeKeypadSlash = 84 + CodeKeypadAsterisk = 85 + CodeKeypadMinus = 86 + CodeKeypadPlus = 87 + CodeKeypadEnter = 88 + CodeKeypad1 = 89 + CodeKeypad2 = 90 + CodeKeypad3 = 91 + CodeKeypad4 = 92 + CodeKeypad5 = 93 + CodeKeypad6 = 94 + CodeKeypad7 = 95 + CodeKeypad8 = 96 + CodeKeypad9 = 97 + CodeKeypad0 = 98 + CodeKeypadFullStop = 99 + CodeKeypadEqualSign = 103 + + CodeF13 = 104 + CodeF14 = 105 + CodeF15 = 106 + CodeF16 = 107 + CodeF17 = 108 + CodeF18 = 109 + CodeF19 = 110 + CodeF20 = 111 + CodeF21 = 112 + CodeF22 = 113 + CodeF23 = 114 + CodeF24 = 115 + + CodeHelp = 117 + + CodeMute = 127 + CodeVolumeUp = 128 + CodeVolumeDown = 129 + + CodeLeftControl = 224 + CodeLeftShift = 225 + CodeLeftAlt = 226 + CodeLeftMeta = 227 + CodeRightControl = 228 + CodeRightShift = 229 + CodeRightAlt = 230 + CodeRightMeta = 231 +) + +// TODO: Given we use runes outside the unicode space, should we provide a +// printing function? Related: it's a little unfortunate that printing a +// key.Event with %v gives not very readable output like: +// {100 7 key.Modifiers() Press} + +var mods = [...]struct { + m Modifiers + s string +}{ + {ModShift, "Shift"}, + {ModControl, "Control"}, + {ModAlt, "Alt"}, + {ModMeta, "Meta"}, +} + +func (m Modifiers) String() string { + var match []string + for _, mod := range mods { + if mod.m&m != 0 { + match = append(match, mod.s) + } + } + return "key.Modifiers(" + strings.Join(match, "|") + ")" +} + +func (d Direction) String() string { + switch d { + case DirNone: + return "None" + case DirPress: + return "Press" + case DirRelease: + return "Release" + default: + return fmt.Sprintf("key.Direction(%d)", d) + } +}