diff --git a/area_unix.go b/area_unix.go index 333f268..3362861 100644 --- a/area_unix.go +++ b/area_unix.go @@ -76,22 +76,32 @@ func our_area_draw_callback(widget *C.GtkWidget, cr *C.cairo_t, data C.gpointer) var area_draw_callback = C.GCallback(C.our_area_draw_callback) +func translateModifiers(state C.guint, window *C.GdkWindow) C.guint { + // GDK doesn't initialize the modifier flags fully; we have to explicitly tell it to (thanks to Daniel_S and daniels (two different people) in irc.gimp.net/#gtk+) + C.gdk_keymap_add_virtual_modifiers( + C.gdk_keymap_get_for_display(C.gdk_window_get_display(window)), + (*C.GdkModifierType)(unsafe.Pointer(&state))) + return state +} + +func makeModifiers(state C.guint) (m Modifiers) { + if (state & C.GDK_CONTROL_MASK) != 0 { + m |= Ctrl + } + if (state & C.GDK_META_MASK) != 0 { + m |= Alt + } + if (state & C.GDK_SHIFT_MASK) != 0 { + m |= Shift + } + return m +} + // shared code for finishing up and sending a mouse event func finishMouseEvent(data C.gpointer, me MouseEvent, mb uint, x C.gdouble, y C.gdouble, state C.guint, gdkwindow *C.GdkWindow) { s := (*sysData)(unsafe.Pointer(data)) - // GDK doesn't initialize the modifier flags fully; we have to explicitly tell it to (thanks to Daniel_S and daniels (two different people) in irc.gimp.net/#gtk+) - C.gdk_keymap_add_virtual_modifiers( - C.gdk_keymap_get_for_display(C.gdk_window_get_display(gdkwindow)), - (*C.GdkModifierType)(unsafe.Pointer(&state))) - if (state & C.GDK_CONTROL_MASK) != 0 { - me.Modifiers |= Ctrl - } - if (state & C.GDK_META_MASK) != 0 { - me.Modifiers |= Alt - } - if (state & C.GDK_SHIFT_MASK) != 0 { - me.Modifiers |= Shift - } + state = translateModifiers(state, gdkwindow) + me.Modifiers = makeModifiers(state) // the mb != # checks exclude the Up/Down button from Held if mb != 1 && (state & C.GDK_BUTTON1_MASK) != 0 { me.Held = append(me.Held, 1) @@ -157,9 +167,38 @@ func our_area_motion_notify_event_callback(widget *C.GtkWidget, event *C.GdkEven var area_motion_notify_event_callback = C.GCallback(C.our_area_motion_notify_event_callback) +// shared code for doing a key event +func doKeyEvent(event *C.GdkEvent, data C.gpointer, up bool) bool { + var ke KeyEvent + + e := (*C.GdkEventKey)(unsafe.Pointer(event)) + s := (*sysData)(unsafe.Pointer(data)) + keyval := e.keyval + if extkey, ok := extkeys[keyval]; ok { + ke.ExtKey = extkey + } else if predef, ok := predefkeys[keyval]; ok { + ke.ASCII = predef + } else if _, ok := modonlykeys[keyval]; !ok { // use ok form here to save memory/avoid racy map write + cp := C.gdk_keyval_to_unicode(keyval) + // GDK keycodes in GDK 3.4 the ASCII plane map to their ASCII values + // (proof: https://git.gnome.org/browse/gtk+/tree/gdk/gdkkeysyms.h?h=gtk-3-4) + // this also handles the numeric keypad keys (proof: https://git.gnome.org/browse/gtk+/tree/gdk/gdkkeyuni.c?h=gtk-3-4#n846) + // the cp < 0x20 will also handle the case where the key is totally unknown to us (gdk_keyval_to_unicode() returns 0) and the space key + if cp < 0x20 || cp >= 0x7F { + // TODO really stop here? or should we handle modifiers? + return false // pretend unhandled + } + ke.ASCII = byte(cp) + } + state := translateModifiers(e.state, e.window) + ke.Modifiers = makeModifiers(state) + ke.Up = up + return s.handler.Key(ke) +} + //export our_area_key_press_event_callback func our_area_key_press_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean { - e := (*C.GdkEventKey)(unsafe.Pointer(event)) +/* fmt.Printf("PRESS %#v\n", e) fmt.Printf("this (%d/GDK_KEY_%s):\n", e.keyval, C.GoString((*C.char)(unsafe.Pointer( @@ -169,28 +208,24 @@ func our_area_key_press_event_callback(widget *C.GtkWidget, event *C.GdkEvent, d pk(C.GDK_KEY_A, e.window) fmt.Printf("%d/GDK_KEY_a:\n", C.GDK_KEY_a) pk(C.GDK_KEY_a, e.window) - return C.FALSE // TODO really false? +*/ + ret := doKeyEvent(event, data, false) + _ = ret + return C.FALSE // TODO really false? should probably return !ret (since true indicates stop processing) } var area_key_press_event_callback = C.GCallback(C.our_area_key_press_event_callback) //export our_area_key_release_event_callback func our_area_key_release_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean { - e := (*C.GdkEventKey)(unsafe.Pointer(event)) - fmt.Printf("RELEASE %#v\n", e) - fmt.Printf("this (%d/GDK_KEY_%s):\n", e.keyval, - C.GoString((*C.char)(unsafe.Pointer( - C.gdk_keyval_name(e.keyval))))) - pk(e.keyval, e.window) - fmt.Printf("%d/GDK_KEY_A:\n", C.GDK_KEY_A) - pk(C.GDK_KEY_A, e.window) - fmt.Printf("%d/GDK_KEY_a:\n", C.GDK_KEY_a) - pk(C.GDK_KEY_a, e.window) - return C.FALSE // TODO really false? + ret := doKeyEvent(event, data, true) + _ = ret + return C.FALSE // TODO really false? should probably return !ret (since true indicates stop processing) } var area_key_release_event_callback = C.GCallback(C.our_area_key_release_event_callback) +/* func pk(keyval C.guint, window *C.GdkWindow) { var kk *C.GdkKeymapKey var nk C.gint @@ -210,3 +245,79 @@ func pk(keyval C.guint, window *C.GdkWindow) { } C.g_free(C.gpointer(unsafe.Pointer(ok))) } +*/ + +var extkeys = map[C.guint]ExtKey{ + C.GDK_KEY_Escape: Escape, + C.GDK_KEY_Insert: Insert, + C.GDK_KEY_Delete: Delete, + C.GDK_KEY_Home: Home, + C.GDK_KEY_End: End, + C.GDK_KEY_Page_Up: PageUp, + C.GDK_KEY_Page_Down: PageDown, + C.GDK_KEY_Up: Up, + C.GDK_KEY_Down: Down, + C.GDK_KEY_Left: Left, + C.GDK_KEY_Right: Right, + C.GDK_KEY_F1: F1, + C.GDK_KEY_F2: F2, + C.GDK_KEY_F3: F3, + C.GDK_KEY_F4: F4, + C.GDK_KEY_F5: F5, + C.GDK_KEY_F6: F6, + C.GDK_KEY_F7: F7, + C.GDK_KEY_F8: F8, + C.GDK_KEY_F9: F9, + C.GDK_KEY_F10: F10, + C.GDK_KEY_F11: F11, + C.GDK_KEY_F12: F12, + // numeric keypad equivalents: + C.GDK_KEY_KP_Insert: Insert, + C.GDK_KEY_KP_Delete: Delete, + C.GDK_KEY_KP_Home: Home, + C.GDK_KEY_KP_End: End, + C.GDK_KEY_KP_Page_Up: PageUp, + C.GDK_KEY_KP_Page_Down: PageDown, + C.GDK_KEY_KP_Up: Up, + C.GDK_KEY_KP_Down: Down, + C.GDK_KEY_KP_Left: Left, + C.GDK_KEY_KP_Right: Right, +} + +// sanity check +func init() { + included := make([]bool, _nextkeys) + for _, v := range extkeys { + included[v] = true + } + for i := 1; i < int(_nextkeys); i++ { + if !included[i] { + panic(fmt.Errorf("error: not all ExtKeys defined on Unix (missing %d)", i)) + } + } +} + +var predefkeys = map[C.guint]byte{ + C.GDK_KEY_Return: '\n', + // TODO C.GDK_KEY_Linefeed too? What key is this? + C.GDK_KEY_Tab: '\t', + C.GDK_KEY_BackSpace: '\b', + // tests indicate that this is sent on Shift+Tab + C.GDK_KEY_ISO_Left_Tab: '\t', + // numeric keypad equivalents: + C.GDK_KEY_KP_Enter: '\n', + // all other numeric keypad equivalents are handled by gdk_keymap_to_unicode() as mentioned above + // no space; handled by the code above +} + +var modonlykeys = map[C.guint]bool{ + C.GDK_KEY_Shift_L: true, + C.GDK_KEY_Shift_R: true, + C.GDK_KEY_Control_L: true, + C.GDK_KEY_Control_R: true, + C.GDK_KEY_Meta_L: true, + C.GDK_KEY_Meta_R: true, + // TODO GDK_KEY_Alt_L/R too? + C.GDK_KEY_Super_L: true, + C.GDK_KEY_Super_R: true, +} diff --git a/test/main.go b/test/main.go index d4a94e3..b2d6a6d 100644 --- a/test/main.go +++ b/test/main.go @@ -133,6 +133,10 @@ func (a *areaHandler) Paint(rect image.Rectangle) *image.NRGBA { func (a *areaHandler) Mouse(e MouseEvent) { // fmt.Printf("%#v\n", e) } +func (a *areaHandler) Key(e KeyEvent) bool { + fmt.Printf("%#v\n", e) + return false +} var doArea = flag.Bool("area", false, "run area test instead") func areaTest() {