x/event.go

482 lines
12 KiB
Go

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
newWidth := int(configureEvent.Width)
newHeight := int(configureEvent.Height)
sizeChanged :=
window.metrics.bounds.Dx() != newWidth ||
window.metrics.bounds.Dy() != newHeight
window.updateBounds()
if sizeChanged {
configureEvent = window.compressConfigureNotify(configureEvent)
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))
}