package x import "image" import "github.com/jezek/xgbutil" import "github.com/jezek/xgb/xproto" import "git.tebibyte.media/tomo/xgbkb" import "github.com/jezek/xgbutil/xevent" import "git.tebibyte.media/tomo/tomo/input" type scrollSum struct { x, y int } // TODO: this needs to be configurable, we need a config api const scrollDistance = 16 func (sum *scrollSum) add (button xproto.Button, window *window, state uint16) { if xgbkb.StateToModifiers(state).Shift { switch button { case 4: sum.x -= scrollDistance case 5: sum.x += scrollDistance case 6: sum.y -= scrollDistance case 7: sum.y += scrollDistance } } else { switch button { case 4: sum.y -= scrollDistance case 5: sum.y += scrollDistance case 6: sum.x -= scrollDistance case 7: sum.x += scrollDistance } } } var buttonCodeTable = map[xproto.Keysym] input.Key { 0xFFFFFF: input.KeyNone, 0xFF63: input.KeyInsert, 0xFF67: input.KeyMenu, 0xFF61: input.KeyPrintScreen, 0xFF6B: input.KeyPause, 0xFFE5: input.KeyCapsLock, 0xFF14: input.KeyScrollLock, 0xFF7F: input.KeyNumLock, 0xFF08: input.KeyBackspace, 0xFF09: input.KeyTab, 0xFE20: input.KeyTab, 0xFF0D: input.KeyEnter, 0xFF1B: input.KeyEscape, 0xFF52: input.KeyUp, 0xFF54: input.KeyDown, 0xFF51: input.KeyLeft, 0xFF53: input.KeyRight, 0xFF55: input.KeyPageUp, 0xFF56: input.KeyPageDown, 0xFF50: input.KeyHome, 0xFF57: input.KeyEnd, 0xFFE1: input.KeyLeftShift, 0xFFE2: input.KeyRightShift, 0xFFE3: input.KeyLeftControl, 0xFFE4: input.KeyRightControl, 0xFFE7: input.KeyLeftMeta, 0xFFE8: input.KeyRightMeta, 0xFFE9: input.KeyLeftAlt, 0xFFEA: input.KeyRightAlt, 0xFFEB: input.KeyLeftSuper, 0xFFEC: input.KeyRightSuper, 0xFFED: input.KeyLeftHyper, 0xFFEE: input.KeyRightHyper, 0xFFFF: input.KeyDelete, 0xFFBE: input.KeyF1, 0xFFBF: input.KeyF2, 0xFFC0: input.KeyF3, 0xFFC1: input.KeyF4, 0xFFC2: input.KeyF5, 0xFFC3: input.KeyF6, 0xFFC4: input.KeyF7, 0xFFC5: input.KeyF8, 0xFFC6: input.KeyF9, 0xFFC7: input.KeyF10, 0xFFC8: input.KeyF11, 0xFFC9: input.KeyF12, 0xFF20: input.KeyDead, } var keypadCodeTable = map[xproto.Keysym] input.Key { 0xff80: input.Key(' '), 0xff89: input.KeyTab, 0xff8d: input.KeyEnter, 0xff91: input.KeyF1, 0xff92: input.KeyF2, 0xff93: input.KeyF3, 0xff94: input.KeyF4, 0xff95: input.KeyHome, 0xff96: input.KeyLeft, 0xff97: input.KeyUp, 0xff98: input.KeyRight, 0xff99: input.KeyDown, 0xff9a: input.KeyPageUp, 0xff9b: input.KeyPageDown, 0xff9c: input.KeyEnd, 0xff9d: input.KeyHome, 0xff9e: input.KeyInsert, 0xff9f: input.KeyDelete, 0xffbd: input.Key('='), 0xffaa: input.Key('*'), 0xffab: input.Key('+'), 0xffac: input.Key(','), 0xffad: input.Key('-'), 0xffae: input.Key('.'), 0xffaf: input.Key('/'), 0xffb0: input.Key('0'), 0xffb1: input.Key('1'), 0xffb2: input.Key('2'), 0xffb3: input.Key('3'), 0xffb4: input.Key('4'), 0xffb5: input.Key('5'), 0xffb6: input.Key('6'), 0xffb7: input.Key('7'), 0xffb8: input.Key('8'), 0xffb9: input.Key('9'), } func (window *window) handleExpose ( connection *xgbutil.XUtil, event xevent.ExposeEvent, ) { if window.xCanvas == nil { window.reallocateCanvas() } window.compressExpose(*event.ExposeEvent) window.pushAll() } func (window *window) updateBounds () { // FIXME: some window managers parent windows more than once, we might // need to sum up all their positions. decorGeometry, _ := window.xWindow.DecorGeometry() windowGeometry, _ := window.xWindow.Geometry() origin := image.Pt( windowGeometry.X() + decorGeometry.X(), windowGeometry.Y() + decorGeometry.Y()) window.metrics.bounds = image.Rectangle { Min: origin, Max: origin.Add(image.Pt(windowGeometry.Width(), windowGeometry.Height())), } } func (window *window) handleConfigureNotify ( connection *xgbutil.XUtil, event xevent.ConfigureNotifyEvent, ) { if window.root == nil { return } configureEvent := *event.ConfigureNotifyEvent configureEvent = window.compressConfigureNotify(configureEvent) oldBounds := window.metrics.bounds window.updateBounds() newBounds := window.metrics.bounds sizeChanged := oldBounds.Dx() != newBounds.Dx() || oldBounds.Dy() != newBounds.Dy() if sizeChanged { window.reallocateCanvas() // TODO figure out what to do with this // if !window.exposeEventFollows(configureEvent) { // } } } func (window *window) exposeEventFollows (event xproto.ConfigureNotifyEvent) (found bool) { nextEvents := xevent.Peek(window.backend.x) if len(nextEvents) > 0 { untypedEvent := nextEvents[0] if untypedEvent.Err == nil { typedEvent, ok := untypedEvent.Event.(xproto.ConfigureNotifyEvent) if ok && typedEvent.Window == event.Window { return true } } } return false } func keycodeToKey (keycode xproto.Keycode, state uint16) (input.Key, bool) { keysym, char := xgbkb.KeycodeToKeysym(keycode, state) if xgbkb.IsOnNumpad(keysym) { // look up in keypad table key := keypadCodeTable[keysym] return key, true } else { // look up in control code table key, isControl := buttonCodeTable[keysym] if isControl { return key, false } else { // return as rune return input.Key(char), false } } } func (window *window) handleKeyPress ( connection *xgbutil.XUtil, event xevent.KeyPressEvent, ) { if window.hasModal { return } keyEvent := *event.KeyPressEvent key, numberPad := keycodeToKey(keyEvent.Detail, keyEvent.State) window.updateModifiers(keyEvent.State) if key == input.KeyTab && window.modifiers.Alt { if window.modifiers.Shift { window.focusPrevious() } else { window.focusNext() } } else if key == input.KeyEscape && window.shy { window.Close() } else if window.focused != nil { window.keyboardTarget().handleKeyDown(key, numberPad) } } func (window *window) handleKeyRelease ( connection *xgbutil.XUtil, event xevent.KeyReleaseEvent, ) { if window.hasModal { return } keyEvent := *event.KeyReleaseEvent // do not process this event if it was generated from a key repeat nextEvents := xevent.Peek(window.backend.x) if len(nextEvents) > 0 { untypedEvent := nextEvents[0] if untypedEvent.Err == nil { typedEvent, ok := untypedEvent.Event.(xproto.KeyPressEvent) if ok && typedEvent.Detail == keyEvent.Detail && typedEvent.Event == keyEvent.Event && typedEvent.State == keyEvent.State { return } } } key, numberPad := keycodeToKey(keyEvent.Detail, keyEvent.State) window.updateModifiers(keyEvent.State) if window.focused != nil { window.keyboardTarget().handleKeyUp(key, numberPad) } } func (window *window) handleButtonPress ( connection *xgbutil.XUtil, event xevent.ButtonPressEvent, ) { if window.hasModal { return } buttonEvent := *event.ButtonPressEvent point := image.Pt(int(buttonEvent.EventX), int(buttonEvent.EventY)) insideWindow := point.In(window.xCanvas.Bounds()) scrolling := buttonEvent.Detail >= 4 && buttonEvent.Detail <= 7 window.updateModifiers(buttonEvent.State) window.updateMousePosition(buttonEvent.EventX, buttonEvent.EventY) if !insideWindow && window.shy && !scrolling { window.Close() } else if scrolling { underneath := window.boxUnder(point, eventCategoryScroll) if underneath != nil { sum := scrollSum { } sum.add(buttonEvent.Detail, window, buttonEvent.State) window.compressScrollSum(buttonEvent, &sum) underneath.handleScroll(float64(sum.x), float64(sum.y)) } } else { underneath := window.boxUnder(point, eventCategoryMouse) window.drags[buttonEvent.Detail] = underneath if underneath != nil { underneath.handleMouseDown(input.Button(buttonEvent.Detail)) } } } func (window *window) handleButtonRelease ( connection *xgbutil.XUtil, event xevent.ButtonReleaseEvent, ) { if window.hasModal { return } buttonEvent := *event.ButtonReleaseEvent if buttonEvent.Detail >= 4 && buttonEvent.Detail <= 7 { return } window.updateModifiers(buttonEvent.State) window.updateMousePosition(buttonEvent.EventX, buttonEvent.EventY) dragging := window.drags[buttonEvent.Detail] window.drags[buttonEvent.Detail] = nil if dragging != nil { dragging.handleMouseUp(input.Button(buttonEvent.Detail)) } } func (window *window) handleMotionNotify ( connection *xgbutil.XUtil, event xevent.MotionNotifyEvent, ) { if window.hasModal { return } motionEvent := window.compressMotionNotify(*event.MotionNotifyEvent) window.updateMousePosition(motionEvent.EventX, motionEvent.EventY) x := int(motionEvent.EventX) y := int(motionEvent.EventY) handled := false for _, child := range window.drags { if child == nil { continue } child.handleMouseMove() handled = true } underneath := window.boxUnder(image.Pt(x, y), eventCategoryMouse) if underneath != nil { window.hover(underneath) if !handled { underneath.handleMouseMove() } } } func (window *window) compressExpose ( firstEvent xproto.ExposeEvent, ) ( lastEvent xproto.ExposeEvent, region image.Rectangle, ) { region = image.Rect ( int(firstEvent.X), int(firstEvent.Y), int(firstEvent.X + firstEvent.Width), int(firstEvent.Y + firstEvent.Height)) window.backend.x.Sync() xevent.Read(window.backend.x, false) lastEvent = firstEvent for index, untypedEvent := range xevent.Peek(window.backend.x) { if untypedEvent.Err != nil { continue } typedEvent, ok := untypedEvent.Event.(xproto.ExposeEvent) if !ok { continue } if firstEvent.Window == typedEvent.Window { region = region.Union (image.Rect ( int(typedEvent.X), int(typedEvent.Y), int(typedEvent.X + typedEvent.Width), int(typedEvent.Y + typedEvent.Height))) lastEvent = typedEvent defer func (index int) { xevent.DequeueAt(window.backend.x, index) } (index) } } return } func (window *window) compressConfigureNotify ( firstEvent xproto.ConfigureNotifyEvent, ) ( lastEvent xproto.ConfigureNotifyEvent, ) { window.backend.x.Sync() xevent.Read(window.backend.x, false) lastEvent = firstEvent for index, untypedEvent := range xevent.Peek(window.backend.x) { if untypedEvent.Err != nil { continue } typedEvent, ok := untypedEvent.Event.(xproto.ConfigureNotifyEvent) if !ok { continue } if firstEvent.Event == typedEvent.Event && firstEvent.Window == typedEvent.Window { lastEvent = typedEvent defer func (index int) { xevent.DequeueAt(window.backend.x, index) } (index) } } return } func (window *window) compressScrollSum ( firstEvent xproto.ButtonPressEvent, sum *scrollSum, ) { window.backend.x.Sync() xevent.Read(window.backend.x, false) for index, untypedEvent := range xevent.Peek(window.backend.x) { if untypedEvent.Err != nil { continue } typedEvent, ok := untypedEvent.Event.(xproto.ButtonPressEvent) if !ok { continue } if firstEvent.Event == typedEvent.Event && typedEvent.Detail >= 4 && typedEvent.Detail <= 7 { sum.add(typedEvent.Detail, window, typedEvent.State) defer func (index int) { xevent.DequeueAt(window.backend.x, index) } (index) } } return } func (window *window) compressMotionNotify ( firstEvent xproto.MotionNotifyEvent, ) ( lastEvent xproto.MotionNotifyEvent, ) { window.backend.x.Sync() xevent.Read(window.backend.x, false) lastEvent = firstEvent for index, untypedEvent := range xevent.Peek(window.backend.x) { if untypedEvent.Err != nil { continue } typedEvent, ok := untypedEvent.Event.(xproto.MotionNotifyEvent) if !ok { continue } if firstEvent.Event == typedEvent.Event { lastEvent = typedEvent defer func (index int) { xevent.DequeueAt(window.backend.x, index) } (index) } } return } func (window *window) updateModifiers (state uint16) { modifiers := xgbkb.StateToModifiers(state) window.modifiers.Shift = modifiers.Shift || modifiers.ShiftLock window.modifiers.Control = modifiers.Control window.modifiers.Alt = modifiers.Alt window.modifiers.Meta = modifiers.Meta window.modifiers.Super = modifiers.Super window.modifiers.Hyper = modifiers.Hyper } func (window *window) updateMousePosition (x, y int16) { window.mousePosition = image.Pt(int(x), int(y)) }