~eliasnaur/gio

abd6e8f9cdd4905e027750dc9d38a1785a3c22ea — pierre a month ago 9684077
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>
M app/internal/wm/os_macos.go => app/internal/wm/os_macos.go +2 -3
@@ 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) {

M app/internal/wm/os_wayland.go => app/internal/wm/os_wayland.go +14 -1
@@ 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)
}

M app/internal/wm/os_windows.go => app/internal/wm/os_windows.go +15 -0
@@ 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)

M app/internal/wm/os_x11.go => app/internal/wm/os_x11.go +53 -10
@@ 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)

M app/window.go => app/window.go +11 -0
@@ 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.