app: support changing Window options at runtime

A Window can now be requested to change its options after
it has been started via its Option method.

All options are supported on macOS, Windows and X11.
On Wayland, only the Size and Title options can be changed
at runtime.

Signed-off-by: pierre <pierre.curto@gmail.com>
This commit is contained in:
pierre
2021-04-06 18:46:29 +02:00
committed by Elias Naur
parent 96840772c7
commit abd6e8f9cd
5 changed files with 95 additions and 14 deletions
+2 -3
View File
@@ -172,11 +172,10 @@ func (w *window) Option(opts *Options) {
func (w *window) SetWindowMode(mode WindowMode) { func (w *window) SetWindowMode(mode WindowMode) {
switch mode { switch mode {
case w.mode: case w.mode:
return case Windowed, Fullscreen:
case Fullscreen:
C.gio_toggleFullScreen(w.window) C.gio_toggleFullScreen(w.window)
w.mode = mode
} }
w.mode = mode
} }
func (w *window) SetCursor(name pointer.CursorName) { func (w *window) SetCursor(name pointer.CursorName) {
+14 -1
View File
@@ -181,6 +181,7 @@ type window struct {
mu sync.Mutex mu sync.Mutex
animating bool animating bool
opts *Options
needAck bool needAck bool
// The most recent configure serial waiting to be ack'ed. // The most recent configure serial waiting to be ack'ed.
serial C.uint32_t serial C.uint32_t
@@ -357,7 +358,7 @@ func (d *wlDisplay) createNativeWindow(opts *Options) (*window, error) {
C.xdg_surface_add_listener(w.wmSurf, &C.gio_xdg_surface_listener, unsafe.Pointer(w.surf)) C.xdg_surface_add_listener(w.wmSurf, &C.gio_xdg_surface_listener, unsafe.Pointer(w.surf))
C.xdg_toplevel_add_listener(w.topLvl, &C.gio_xdg_toplevel_listener, unsafe.Pointer(w.surf)) C.xdg_toplevel_add_listener(w.topLvl, &C.gio_xdg_toplevel_listener, unsafe.Pointer(w.surf))
w.Option(opts) w.setOptions(opts)
if d.decor != nil { if d.decor != nil {
// Request server side decorations. // Request server side decorations.
@@ -910,6 +911,13 @@ func (w *window) WriteClipboard(s string) {
} }
func (w *window) Option(opts *Options) { func (w *window) Option(opts *Options) {
w.mu.Lock()
w.opts = opts
w.mu.Unlock()
w.disp.wakeup()
}
func (w *window) setOptions(opts *Options) {
_, _, cfg := w.config() _, _, cfg := w.config()
if o := opts.Size; o != nil { if o := opts.Size; o != nil {
w.width = cfg.Px(o.Width) w.width = cfg.Px(o.Width)
@@ -1143,8 +1151,10 @@ func (w *window) process() {
w.mu.Lock() w.mu.Lock()
readClipboard := w.readClipboard readClipboard := w.readClipboard
writeClipboard := w.writeClipboard writeClipboard := w.writeClipboard
opts := w.opts
w.readClipboard = false w.readClipboard = false
w.writeClipboard = nil w.writeClipboard = nil
w.opts = nil
w.mu.Unlock() w.mu.Unlock()
if readClipboard { if readClipboard {
r, err := w.disp.readClipboard() r, err := w.disp.readClipboard()
@@ -1163,6 +1173,9 @@ func (w *window) process() {
if writeClipboard != nil { if writeClipboard != nil {
w.disp.writeClipboard([]byte(*writeClipboard)) w.disp.writeClipboard([]byte(*writeClipboard))
} }
if opts != nil {
w.setOptions(opts)
}
// pass false to skip unnecessary drawing. // pass false to skip unnecessary drawing.
w.draw(false) w.draw(false)
} }
+15
View File
@@ -66,6 +66,7 @@ type window struct {
const ( const (
_WM_REDRAW = windows.WM_USER + iota _WM_REDRAW = windows.WM_USER + iota
_WM_CURSOR _WM_CURSOR
_WM_OPTION
) )
type gpuAPI struct { type gpuAPI struct {
@@ -317,6 +318,8 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
windows.SetCursor(w.cursor) windows.SetCursor(w.cursor)
return windows.TRUE return windows.TRUE
} }
case _WM_OPTION:
w.setOptions()
} }
return windows.DefWindowProc(hwnd, msg, wParam, lParam) return windows.DefWindowProc(hwnd, msg, wParam, lParam)
@@ -520,6 +523,18 @@ func (w *window) readClipboard() error {
} }
func (w *window) Option(opts *Options) { func (w *window) Option(opts *Options) {
w.mu.Lock()
w.opts = opts
w.mu.Unlock()
if err := windows.PostMessage(w.hwnd, _WM_OPTION, 0, 0); err != nil {
panic(err)
}
}
func (w *window) setOptions() {
w.mu.Lock()
opts := w.opts
w.mu.Unlock()
if o := opts.Size; o != nil { if o := opts.Size; o != nil {
dpi := windows.GetSystemDPI() dpi := windows.GetSystemDPI()
cfg := configForDPI(dpi) cfg := configForDPI(dpi)
+53 -10
View File
@@ -89,6 +89,7 @@ type x11Window struct {
mu sync.Mutex mu sync.Mutex
animating bool animating bool
opts *Options
pointerBtns pointer.Buttons pointerBtns pointer.Buttons
@@ -98,6 +99,7 @@ type x11Window struct {
content []byte content []byte
} }
cursor pointer.CursorName cursor pointer.CursorName
mode WindowMode
} }
func (w *x11Window) SetAnimating(anim bool) { func (w *x11Window) SetAnimating(anim bool) {
@@ -124,22 +126,33 @@ func (w *x11Window) WriteClipboard(s string) {
} }
func (w *x11Window) Option(opts *Options) { func (w *x11Window) Option(opts *Options) {
dpy := w.x w.mu.Lock()
win := w.xw w.opts = opts
cfg := w.cfg w.mu.Unlock()
w.wakeup()
}
func (w *x11Window) setOptions() {
w.mu.Lock()
opts := w.opts
w.opts = nil
w.mu.Unlock()
if opts == nil {
return
}
var shints C.XSizeHints var shints C.XSizeHints
if o := opts.MinSize; o != nil { if o := opts.MinSize; o != nil {
shints.min_width = C.int(cfg.Px(o.Width)) shints.min_width = C.int(w.cfg.Px(o.Width))
shints.min_height = C.int(cfg.Px(o.Height)) shints.min_height = C.int(w.cfg.Px(o.Height))
shints.flags = C.PMinSize shints.flags = C.PMinSize
} }
if o := opts.MaxSize; o != nil { if o := opts.MaxSize; o != nil {
shints.max_width = C.int(cfg.Px(o.Width)) shints.max_width = C.int(w.cfg.Px(o.Width))
shints.max_height = C.int(cfg.Px(o.Height)) shints.max_height = C.int(w.cfg.Px(o.Height))
shints.flags = shints.flags | C.PMaxSize shints.flags = shints.flags | C.PMaxSize
} }
if shints.flags != 0 { if shints.flags != 0 {
C.XSetWMNormalHints(dpy, win, &shints) C.XSetWMNormalHints(w.x, w.xw, &shints)
} }
var title string var title string
@@ -148,9 +161,9 @@ func (w *x11Window) Option(opts *Options) {
} }
ctitle := C.CString(title) ctitle := C.CString(title)
defer C.free(unsafe.Pointer(ctitle)) defer C.free(unsafe.Pointer(ctitle))
C.XStoreName(dpy, win, ctitle) C.XStoreName(w.x, w.xw, ctitle)
// set _NET_WM_NAME as well for UTF-8 support in window title. // set _NET_WM_NAME as well for UTF-8 support in window title.
C.XSetTextProperty(dpy, win, C.XSetTextProperty(w.x, w.xw,
&C.XTextProperty{ &C.XTextProperty{
value: (*C.uchar)(unsafe.Pointer(ctitle)), value: (*C.uchar)(unsafe.Pointer(ctitle)),
encoding: w.atoms.utf8string, encoding: w.atoms.utf8string,
@@ -190,6 +203,8 @@ func (w *x11Window) SetCursor(name pointer.CursorName) {
func (w *x11Window) SetWindowMode(mode WindowMode) { func (w *x11Window) SetWindowMode(mode WindowMode) {
switch mode { switch mode {
case w.mode:
return
case Windowed: case Windowed:
C.XDeleteProperty(w.x, w.xw, w.atoms.wmStateFullscreen) C.XDeleteProperty(w.x, w.xw, w.atoms.wmStateFullscreen)
case Fullscreen: case Fullscreen:
@@ -197,7 +212,34 @@ func (w *x11Window) SetWindowMode(mode WindowMode) {
32, C.PropModeReplace, 32, C.PropModeReplace,
(*C.uchar)(unsafe.Pointer(&w.atoms.wmStateFullscreen)), 1, (*C.uchar)(unsafe.Pointer(&w.atoms.wmStateFullscreen)), 1,
) )
default:
return
} }
w.mode = mode
// "A Client wishing to change the state of a window MUST send
// a _NET_WM_STATE client message to the root window (see below)."
var xev C.XEvent
ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
*ev = C.XClientMessageEvent{
_type: C.ClientMessage,
display: w.x,
window: w.xw,
message_type: w.atoms.wmState,
format: 32,
}
arr := (*[5]C.long)(unsafe.Pointer(&ev.data))
arr[0] = 2 // _NET_WM_STATE_TOGGLE
arr[1] = C.long(w.atoms.wmStateFullscreen)
arr[2] = 0
arr[3] = 1 // application
arr[4] = 0
C.XSendEvent(
w.x,
C.XDefaultRootWindow(w.x), // MUST be the root window
C.False,
C.SubstructureNotifyMask|C.SubstructureRedirectMask,
&xev,
)
} }
func (w *x11Window) ShowTextInput(show bool) {} func (w *x11Window) ShowTextInput(show bool) {}
@@ -287,6 +329,7 @@ loop:
} }
} }
} }
w.setOptions()
// Clear notifications. // Clear notifications.
for { for {
_, err := syscall.Read(w.notify.read, buf) _, err := syscall.Read(w.notify.read, buf)
+11
View File
@@ -211,6 +211,17 @@ func (w *Window) Invalidate() {
} }
} }
// Option applies the options to the window.
func (w *Window) Option(opts ...Option) {
go w.driverDo(func() {
o := new(wm.Options)
for _, opt := range opts {
opt(o)
}
w.driver.Option(o)
})
}
// ReadClipboard initiates a read of the clipboard in the form // ReadClipboard initiates a read of the clipboard in the form
// of a clipboard.Event. Multiple reads may be coalesced // of a clipboard.Event. Multiple reads may be coalesced
// to a single event. // to a single event.