483 lines
12 KiB
Go
483 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
|
|
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))
|
|
}
|