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) {
switch mode {
case w.mode:
return
case Fullscreen:
case Windowed, Fullscreen:
C.gio_toggleFullScreen(w.window)
w.mode = mode
}
w.mode = mode
}
func (w *window) SetCursor(name pointer.CursorName) {
+14 -1
View File
@@ -181,6 +181,7 @@ type window struct {
mu sync.Mutex
animating bool
opts *Options
needAck bool
// The most recent configure serial waiting to be ack'ed.
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_toplevel_add_listener(w.topLvl, &C.gio_xdg_toplevel_listener, unsafe.Pointer(w.surf))
w.Option(opts)
w.setOptions(opts)
if d.decor != nil {
// Request server side decorations.
@@ -910,6 +911,13 @@ func (w *window) WriteClipboard(s string) {
}
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()
if o := opts.Size; o != nil {
w.width = cfg.Px(o.Width)
@@ -1143,8 +1151,10 @@ func (w *window) process() {
w.mu.Lock()
readClipboard := w.readClipboard
writeClipboard := w.writeClipboard
opts := w.opts
w.readClipboard = false
w.writeClipboard = nil
w.opts = nil
w.mu.Unlock()
if readClipboard {
r, err := w.disp.readClipboard()
@@ -1163,6 +1173,9 @@ func (w *window) process() {
if writeClipboard != nil {
w.disp.writeClipboard([]byte(*writeClipboard))
}
if opts != nil {
w.setOptions(opts)
}
// pass false to skip unnecessary drawing.
w.draw(false)
}
+15
View File
@@ -66,6 +66,7 @@ type window struct {
const (
_WM_REDRAW = windows.WM_USER + iota
_WM_CURSOR
_WM_OPTION
)
type gpuAPI struct {
@@ -317,6 +318,8 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
windows.SetCursor(w.cursor)
return windows.TRUE
}
case _WM_OPTION:
w.setOptions()
}
return windows.DefWindowProc(hwnd, msg, wParam, lParam)
@@ -520,6 +523,18 @@ func (w *window) readClipboard() error {
}
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 {
dpi := windows.GetSystemDPI()
cfg := configForDPI(dpi)
+53 -10
View File
@@ -89,6 +89,7 @@ type x11Window struct {
mu sync.Mutex
animating bool
opts *Options
pointerBtns pointer.Buttons
@@ -98,6 +99,7 @@ type x11Window struct {
content []byte
}
cursor pointer.CursorName
mode WindowMode
}
func (w *x11Window) SetAnimating(anim bool) {
@@ -124,22 +126,33 @@ func (w *x11Window) WriteClipboard(s string) {
}
func (w *x11Window) Option(opts *Options) {
dpy := w.x
win := w.xw
cfg := w.cfg
w.mu.Lock()
w.opts = opts
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
if o := opts.MinSize; o != nil {
shints.min_width = C.int(cfg.Px(o.Width))
shints.min_height = C.int(cfg.Px(o.Height))
shints.min_width = C.int(w.cfg.Px(o.Width))
shints.min_height = C.int(w.cfg.Px(o.Height))
shints.flags = C.PMinSize
}
if o := opts.MaxSize; o != nil {
shints.max_width = C.int(cfg.Px(o.Width))
shints.max_height = C.int(cfg.Px(o.Height))
shints.max_width = C.int(w.cfg.Px(o.Width))
shints.max_height = C.int(w.cfg.Px(o.Height))
shints.flags = shints.flags | C.PMaxSize
}
if shints.flags != 0 {
C.XSetWMNormalHints(dpy, win, &shints)
C.XSetWMNormalHints(w.x, w.xw, &shints)
}
var title string
@@ -148,9 +161,9 @@ func (w *x11Window) Option(opts *Options) {
}
ctitle := C.CString(title)
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.
C.XSetTextProperty(dpy, win,
C.XSetTextProperty(w.x, w.xw,
&C.XTextProperty{
value: (*C.uchar)(unsafe.Pointer(ctitle)),
encoding: w.atoms.utf8string,
@@ -190,6 +203,8 @@ func (w *x11Window) SetCursor(name pointer.CursorName) {
func (w *x11Window) SetWindowMode(mode WindowMode) {
switch mode {
case w.mode:
return
case Windowed:
C.XDeleteProperty(w.x, w.xw, w.atoms.wmStateFullscreen)
case Fullscreen:
@@ -197,7 +212,34 @@ func (w *x11Window) SetWindowMode(mode WindowMode) {
32, C.PropModeReplace,
(*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) {}
@@ -287,6 +329,7 @@ loop:
}
}
}
w.setOptions()
// Clear notifications.
for {
_, 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
// of a clipboard.Event. Multiple reads may be coalesced
// to a single event.