330 lines
8.3 KiB
Go
330 lines
8.3 KiB
Go
|
package x
|
||
|
|
||
|
import "image"
|
||
|
// import "errors"
|
||
|
|
||
|
import "git.tebibyte.media/tomo/tomo"
|
||
|
import "git.tebibyte.media/tomo/tomo/data"
|
||
|
import "git.tebibyte.media/tomo/tomo/input"
|
||
|
import "git.tebibyte.media/tomo/tomo/event"
|
||
|
|
||
|
import "github.com/jezek/xgb/xproto"
|
||
|
import "github.com/jezek/xgbutil/ewmh"
|
||
|
import "github.com/jezek/xgbutil/icccm"
|
||
|
// import "github.com/jezek/xgbutil/xprop"
|
||
|
import "github.com/jezek/xgbutil/xevent"
|
||
|
import "github.com/jezek/xgbutil/xwindow"
|
||
|
import "github.com/jezek/xgbutil/keybind"
|
||
|
import "github.com/jezek/xgbutil/mousebind"
|
||
|
import "github.com/jezek/xgbutil/xgraphics"
|
||
|
|
||
|
type mainWindow struct { *window }
|
||
|
type window struct {
|
||
|
backend *Backend
|
||
|
xWindow *xwindow.Window
|
||
|
xCanvas *xgraphics.Image
|
||
|
|
||
|
title string
|
||
|
|
||
|
modalParent *window
|
||
|
hasModal bool
|
||
|
shy bool
|
||
|
|
||
|
metrics struct {
|
||
|
bounds image.Rectangle
|
||
|
}
|
||
|
|
||
|
modifiers input.Modifiers
|
||
|
mousePosition image.Point
|
||
|
|
||
|
onClose event.FuncBroadcaster
|
||
|
|
||
|
root anyBox
|
||
|
focused anyBox
|
||
|
}
|
||
|
|
||
|
func (backend *Backend) NewWindow (
|
||
|
bounds image.Rectangle,
|
||
|
) (
|
||
|
output tomo.MainWindow,
|
||
|
err error,
|
||
|
) {
|
||
|
if backend == nil { panic("nil backend") }
|
||
|
window, err := backend.newWindow(bounds, false)
|
||
|
|
||
|
output = mainWindow { window: window }
|
||
|
return output, err
|
||
|
}
|
||
|
|
||
|
func (backend *Backend) newWindow (
|
||
|
bounds image.Rectangle,
|
||
|
override bool,
|
||
|
) (
|
||
|
output *window,
|
||
|
err error,
|
||
|
) {
|
||
|
if bounds.Dx() == 0 { bounds.Max.X = bounds.Min.X + 8 }
|
||
|
if bounds.Dy() == 0 { bounds.Max.Y = bounds.Min.Y + 8 }
|
||
|
|
||
|
window := &window { backend: backend }
|
||
|
|
||
|
window.xWindow, err = xwindow.Generate(backend.x)
|
||
|
if err != nil { return }
|
||
|
|
||
|
if override {
|
||
|
err = window.xWindow.CreateChecked (
|
||
|
backend.x.RootWin(),
|
||
|
bounds.Min.X, bounds.Min.Y, bounds.Dx(), bounds.Dy(),
|
||
|
xproto.CwOverrideRedirect, 1)
|
||
|
} else {
|
||
|
err = window.xWindow.CreateChecked (
|
||
|
backend.x.RootWin(),
|
||
|
bounds.Min.X, bounds.Min.Y, bounds.Dx(), bounds.Dy(), 0)
|
||
|
}
|
||
|
if err != nil { return }
|
||
|
|
||
|
err = window.xWindow.Listen (
|
||
|
xproto.EventMaskExposure,
|
||
|
xproto.EventMaskStructureNotify,
|
||
|
xproto.EventMaskPropertyChange,
|
||
|
xproto.EventMaskPointerMotion,
|
||
|
xproto.EventMaskKeyPress,
|
||
|
xproto.EventMaskKeyRelease,
|
||
|
xproto.EventMaskButtonPress,
|
||
|
xproto.EventMaskButtonRelease)
|
||
|
if err != nil { return }
|
||
|
|
||
|
window.xWindow.WMGracefulClose (func (xWindow *xwindow.Window) {
|
||
|
window.Close()
|
||
|
})
|
||
|
|
||
|
// xevent.ExposeFun(window.handleExpose).
|
||
|
// Connect(backend.x, window.xWindow.Id)
|
||
|
// xevent.ConfigureNotifyFun(window.handleConfigureNotify).
|
||
|
// Connect(backend.x, window.xWindow.Id)
|
||
|
// xevent.KeyPressFun(window.handleKeyPress).
|
||
|
// Connect(backend.x, window.xWindow.Id)
|
||
|
// xevent.KeyReleaseFun(window.handleKeyRelease).
|
||
|
// Connect(backend.x, window.xWindow.Id)
|
||
|
// xevent.ButtonPressFun(window.handleButtonPress).
|
||
|
// Connect(backend.x, window.xWindow.Id)
|
||
|
// xevent.ButtonReleaseFun(window.handleButtonRelease).
|
||
|
// Connect(backend.x, window.xWindow.Id)
|
||
|
// xevent.MotionNotifyFun(window.handleMotionNotify).
|
||
|
// Connect(backend.x, window.xWindow.Id)
|
||
|
// xevent.SelectionNotifyFun(window.handleSelectionNotify).
|
||
|
// Connect(backend.x, window.xWindow.Id)
|
||
|
// xevent.PropertyNotifyFun(window.handlePropertyNotify).
|
||
|
// Connect(backend.x, window.xWindow.Id)
|
||
|
// xevent.SelectionClearFun(window.handleSelectionClear).
|
||
|
// Connect(backend.x, window.xWindow.Id)
|
||
|
// xevent.SelectionRequestFun(window.handleSelectionRequest).
|
||
|
// Connect(backend.x, window.xWindow.Id)
|
||
|
|
||
|
window.metrics.bounds = bounds
|
||
|
window.setMinimumSize(8, 8)
|
||
|
|
||
|
// window.reallocateCanvas()
|
||
|
|
||
|
backend.windows[window.xWindow.Id] = window
|
||
|
|
||
|
output = window
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (window *window) SetTitle (title string) {
|
||
|
window.title = title
|
||
|
ewmh .WmNameSet (window.backend.x, window.xWindow.Id, title)
|
||
|
icccm.WmNameSet (window.backend.x, window.xWindow.Id, title)
|
||
|
icccm.WmIconNameSet (window.backend.x, window.xWindow.Id, title)
|
||
|
}
|
||
|
|
||
|
func (window *window) SetIcon (sizes []image.Image) {
|
||
|
wmIcons := []ewmh.WmIcon { }
|
||
|
|
||
|
for _, icon := range sizes {
|
||
|
width := icon.Bounds().Max.X
|
||
|
height := icon.Bounds().Max.Y
|
||
|
wmIcon := ewmh.WmIcon {
|
||
|
Width: uint(width),
|
||
|
Height: uint(height),
|
||
|
Data: make([]uint, width * height),
|
||
|
}
|
||
|
|
||
|
// manually convert image data beacuse of course we have to do
|
||
|
// this
|
||
|
index := 0
|
||
|
for y := 0; y < height; y ++ {
|
||
|
for x := 0; x < width; x ++ {
|
||
|
r, g, b, a := icon.At(x, y).RGBA()
|
||
|
r >>= 8
|
||
|
g >>= 8
|
||
|
b >>= 8
|
||
|
a >>= 8
|
||
|
wmIcon.Data[index] =
|
||
|
(uint(a) << 24) |
|
||
|
(uint(r) << 16) |
|
||
|
(uint(g) << 8) |
|
||
|
(uint(b) << 0)
|
||
|
index ++
|
||
|
}}
|
||
|
|
||
|
wmIcons = append(wmIcons, wmIcon)
|
||
|
}
|
||
|
|
||
|
ewmh.WmIconSet (
|
||
|
window.backend.x,
|
||
|
window.xWindow.Id,
|
||
|
wmIcons)
|
||
|
}
|
||
|
|
||
|
func (window *window) NewMenu (bounds image.Rectangle) (tomo.Window, error) {
|
||
|
menu, err := window.backend.newWindow (
|
||
|
bounds.Add(window.metrics.bounds.Min), true)
|
||
|
menu.shy = true
|
||
|
icccm.WmTransientForSet (
|
||
|
window.backend.x,
|
||
|
menu.xWindow.Id,
|
||
|
window.xWindow.Id)
|
||
|
menu.setType("POPUP_MENU")
|
||
|
// menu.inheritProperties(window)
|
||
|
return menu, err
|
||
|
}
|
||
|
|
||
|
func (window *window) NewModal (bounds image.Rectangle) (tomo.Window, error) {
|
||
|
modal, err := window.backend.newWindow (
|
||
|
bounds.Add(window.metrics.bounds.Min), false)
|
||
|
icccm.WmTransientForSet (
|
||
|
window.backend.x,
|
||
|
modal.xWindow.Id,
|
||
|
window.xWindow.Id)
|
||
|
ewmh.WmStateSet (
|
||
|
window.backend.x,
|
||
|
modal.xWindow.Id,
|
||
|
[]string { "_NET_WM_STATE_MODAL" })
|
||
|
modal.modalParent = window
|
||
|
window.hasModal = true
|
||
|
// modal.inheritProperties(window)
|
||
|
return modal, err
|
||
|
}
|
||
|
|
||
|
func (window mainWindow) NewChild (bounds image.Rectangle) (tomo.Window, error) {
|
||
|
child, err := window.backend.newWindow (
|
||
|
bounds.Add(window.metrics.bounds.Min), false)
|
||
|
if err != nil { return nil, err }
|
||
|
child.setClientLeader(window.window)
|
||
|
window.setClientLeader(window.window)
|
||
|
icccm.WmTransientForSet (
|
||
|
window.backend.x,
|
||
|
window.xWindow.Id,
|
||
|
window.xWindow.Id)
|
||
|
window.setType("UTILITY")
|
||
|
// window.inheritProperties(window.window)
|
||
|
return window, err
|
||
|
}
|
||
|
|
||
|
func (window *window) Widget () (tomo.Window, error) {
|
||
|
// TODO
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
func (window *window) Copy (data.Data) {
|
||
|
// TODO
|
||
|
}
|
||
|
|
||
|
func (window *window) Paste (callback func (data.Data, error), accept ...data.Mime) {
|
||
|
// TODO
|
||
|
}
|
||
|
|
||
|
func (window *window) Show () {
|
||
|
// if window.child == nil {
|
||
|
// window.xCanvas.For (func (x, y int) xgraphics.BGRA {
|
||
|
// return xgraphics.BGRA { }
|
||
|
// })
|
||
|
//
|
||
|
// window.pushRegion(window.xCanvas.Bounds())
|
||
|
// }
|
||
|
|
||
|
window.xWindow.Map()
|
||
|
if window.shy { window.grabInput() }
|
||
|
}
|
||
|
|
||
|
func (window *window) Hide () {
|
||
|
window.xWindow.Unmap()
|
||
|
if window.shy { window.ungrabInput() }
|
||
|
}
|
||
|
|
||
|
func (window *window) Close () {
|
||
|
xevent .Detach(window.backend.x, window.xWindow.Id)
|
||
|
keybind .Detach(window.backend.x, window.xWindow.Id)
|
||
|
mousebind.Detach(window.backend.x, window.xWindow.Id)
|
||
|
|
||
|
window.onClose.Broadcast()
|
||
|
if window.modalParent != nil {
|
||
|
// we are a modal dialog, so unlock the parent
|
||
|
window.modalParent.hasModal = false
|
||
|
}
|
||
|
window.Hide()
|
||
|
// window.Adopt(nil)
|
||
|
delete(window.backend.windows, window.xWindow.Id)
|
||
|
window.xWindow.Destroy()
|
||
|
}
|
||
|
|
||
|
func (window *window) OnClose (callback func ()) event.Cookie {
|
||
|
return window.onClose.Connect(callback)
|
||
|
}
|
||
|
|
||
|
func (window *window) grabInput () {
|
||
|
keybind.GrabKeyboard(window.backend.x, window.xWindow.Id)
|
||
|
mousebind.GrabPointer (
|
||
|
window.backend.x,
|
||
|
window.xWindow.Id,
|
||
|
window.backend.x.RootWin(), 0)
|
||
|
}
|
||
|
|
||
|
func (window *window) ungrabInput () {
|
||
|
keybind.UngrabKeyboard(window.backend.x)
|
||
|
mousebind.UngrabPointer(window.backend.x)
|
||
|
}
|
||
|
|
||
|
func (window *window) setType (ty string) error {
|
||
|
return ewmh.WmWindowTypeSet (
|
||
|
window.backend.x,
|
||
|
window.xWindow.Id,
|
||
|
[]string { "_NET_WM_WINDOW_TYPE_" + ty })
|
||
|
}
|
||
|
|
||
|
func (window *window) setClientLeader (leader *window) error {
|
||
|
hints, _ := icccm.WmHintsGet(window.backend.x, window.xWindow.Id)
|
||
|
if hints == nil {
|
||
|
hints = &icccm.Hints { }
|
||
|
}
|
||
|
hints.Flags |= icccm.HintWindowGroup
|
||
|
hints.WindowGroup = leader.xWindow.Id
|
||
|
return icccm.WmHintsSet (
|
||
|
window.backend.x,
|
||
|
window.xWindow.Id,
|
||
|
hints)
|
||
|
}
|
||
|
|
||
|
func (window *window) setMinimumSize (width, height int) {
|
||
|
if width < 8 { width = 8 }
|
||
|
if height < 8 { height = 8 }
|
||
|
icccm.WmNormalHintsSet (
|
||
|
window.backend.x,
|
||
|
window.xWindow.Id,
|
||
|
&icccm.NormalHints {
|
||
|
Flags: icccm.SizeHintPMinSize,
|
||
|
MinWidth: uint(width),
|
||
|
MinHeight: uint(height),
|
||
|
})
|
||
|
newWidth := window.metrics.bounds.Dx()
|
||
|
newHeight := window.metrics.bounds.Dy()
|
||
|
if newWidth < width { newWidth = width }
|
||
|
if newHeight < height { newHeight = height }
|
||
|
if newWidth != window.metrics.bounds.Dx() ||
|
||
|
newHeight != window.metrics.bounds.Dy() {
|
||
|
window.xWindow.Resize(newWidth, newHeight)
|
||
|
}
|
||
|
}
|