Skip to content

Commit

Permalink
app: use material.Decorations on undecorated platforms
Browse files Browse the repository at this point in the history
This patch implements a mechanism for customizing window
decorations.
If a window is configured with app.Decorated(true), then
the widget/material.Decorations are applied. On Wayland,
the option is automatically set when the server does not
provide window decorations.

Server side decorations are no longer requested.
The Decorated flag is set according to the
server's requests.

Wayland is now the default driver for UNIX platforms.

References: https://todo.sr.ht/~eliasnaur/gio/318
Signed-off-by: Pierre Curto <[email protected]>
  • Loading branch information
pierrec authored and eliasnaur committed Jan 27, 2022
1 parent 90ad001 commit 5ce1e98
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 16 deletions.
14 changes: 14 additions & 0 deletions app/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ type Config struct {
CustomRenderer bool
// center is a flag used to center the window. Set by option.
center bool
// Decorated reports whether window decorations are provided automatically.
Decorated bool
}

// ConfigEvent is sent whenever the configuration of a Window changes.
Expand Down Expand Up @@ -177,6 +179,9 @@ type driver interface {

// Wakeup wakes up the event loop and sends a WakeupEvent.
Wakeup()

// Perform actions on the window.
Perform(system.Action)
}

type windowRendezvous struct {
Expand Down Expand Up @@ -218,3 +223,12 @@ func newWindowRendezvous() *windowRendezvous {

func (wakeupEvent) ImplementsEvent() {}
func (ConfigEvent) ImplementsEvent() {}

func walkActions(actions system.Action, do func(system.Action)) {
for a := system.Action(1); actions != 0; a <<= 1 {
if actions&a != 0 {
actions &^= a
do(a)
}
}
}
8 changes: 8 additions & 0 deletions app/os_android.go
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,9 @@ func (w *window) Configure(options []Option) {
prev := w.config
cnf := w.config
cnf.apply(unit.Metric{}, options)
// Decorations are never disabled.
cnf.Decorated = true

if prev.Orientation != cnf.Orientation {
w.config.Orientation = cnf.Orientation
setOrientation(env, w.view, cnf.Orientation)
Expand All @@ -1188,12 +1191,17 @@ func (w *window) Configure(options []Option) {
w.config.Mode = Windowed
}
}
if cnf.Decorated != prev.Decorated {
w.config.Decorated = cnf.Decorated
}
if w.config != prev {
w.callbacks.Event(ConfigEvent{Config: w.config})
}
})
}

func (w *window) Perform(system.Action) {}

func (w *window) Raise() {}

func (w *window) SetCursor(name pointer.CursorName) {
Expand Down
12 changes: 11 additions & 1 deletion app/os_ios.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ type window struct {

visible bool
cursor pointer.CursorName
config Config

pointerMap []C.CFTypeRef
}
Expand Down Expand Up @@ -273,7 +274,16 @@ func (w *window) WriteClipboard(s string) {
C.writeClipboard(chars, C.NSUInteger(len(u16)))
}

func (w *window) Configure([]Option) {}
func (w *window) Configure([]Option) {
prev := w.config
// Decorations are never disabled.
w.config.Decorated = true
if w.config != prev {
w.w.Event(ConfigEvent{Config: w.config})
}
}

func (w *window) Perform(system.Action) {}

func (w *window) Raise() {}

Expand Down
8 changes: 8 additions & 0 deletions app/os_js.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,9 @@ func (w *window) Configure(options []Option) {
prev := w.config
cnf := w.config
cnf.apply(unit.Metric{}, options)
// Decorations are never disabled.
cnf.Decorated = true

if prev.Title != cnf.Title {
w.config.Title = cnf.Title
w.document.Set("title", cnf.Title)
Expand All @@ -528,11 +531,16 @@ func (w *window) Configure(options []Option) {
w.config.Orientation = cnf.Orientation
w.orientation(cnf.Orientation)
}
if cnf.Decorated != prev.Decorated {
w.config.Decorated = cnf.Decorated
}
if w.config != prev {
w.w.Event(ConfigEvent{Config: w.config})
}
}

func (w *window) Perform(system.Action) {}

func (w *window) Raise() {}

func (w *window) SetCursor(name pointer.CursorName) {
Expand Down
7 changes: 7 additions & 0 deletions app/os_macos.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ func (w *window) Configure(options []Option) {
cnf.Size = cnf.Size.Div(int(screenScale))
cnf.MinSize = cnf.MinSize.Div(int(screenScale))
cnf.MaxSize = cnf.MaxSize.Div(int(screenScale))
// Decorations are never disabled.
cnf.Decorated = true

switch cnf.Mode {
case Fullscreen:
Expand Down Expand Up @@ -325,6 +327,9 @@ func (w *window) Configure(options []Option) {
C.setScreenFrame(w.window, C.CGFloat(x), C.CGFloat(y), C.CGFloat(sz.X), C.CGFloat(sz.Y))
}
}
if cnf.Decorated != prev.Decorated {
w.config.Decorated = cnf.Decorated
}
if w.config != prev {
w.w.Event(ConfigEvent{Config: w.config})
}
Expand All @@ -339,6 +344,8 @@ func (w *window) setTitle(prev, cnf Config) {
}
}

func (w *window) Perform(system.Action) {}

func (w *window) SetCursor(name pointer.CursorName) {
w.cursor = windowSetCursor(w.cursor, name)
}
Expand Down
2 changes: 1 addition & 1 deletion app/os_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var wlDriver, x11Driver windowDriver

func newWindow(window *callbacks, options []Option) error {
var errFirst error
for _, d := range []windowDriver{x11Driver, wlDriver} {
for _, d := range []windowDriver{wlDriver, x11Driver} {
if d == nil {
continue
}
Expand Down
5 changes: 5 additions & 0 deletions app/os_wayland.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <wayland-client.h>
#include "wayland_xdg_shell.h"
#include "wayland_xdg_decoration.h"
#include "wayland_text_input.h"
#include "_cgo_export.h"

Expand All @@ -29,6 +30,10 @@ const struct xdg_toplevel_listener gio_xdg_toplevel_listener = {
.close = gio_onToplevelClose,
};

const struct zxdg_toplevel_decoration_v1_listener gio_zxdg_toplevel_decoration_v1_listener = {
.configure = gio_onToplevelDecorationConfigure,
};

static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm, uint32_t serial) {
xdg_wm_base_pong(wm, serial);
}
Expand Down
103 changes: 96 additions & 7 deletions app/os_wayland.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ extern const struct wl_registry_listener gio_registry_listener;
extern const struct wl_surface_listener gio_surface_listener;
extern const struct xdg_surface_listener gio_xdg_surface_listener;
extern const struct xdg_toplevel_listener gio_xdg_toplevel_listener;
extern const struct zxdg_toplevel_decoration_v1_listener gio_zxdg_toplevel_decoration_v1_listener;
extern const struct xdg_wm_base_listener gio_xdg_wm_base_listener;
extern const struct wl_callback_listener gio_callback_listener;
extern const struct wl_output_listener gio_output_listener;
Expand Down Expand Up @@ -149,6 +150,7 @@ type repeatState struct {
type window struct {
w *callbacks
disp *wlDisplay
seat *wlSeat
surf *C.struct_wl_surface
wmSurf *C.struct_xdg_surface
topLvl *C.struct_xdg_toplevel
Expand Down Expand Up @@ -188,9 +190,10 @@ type window struct {
newScale bool
scale int
// size is the unscaled window size (unlike config.Size which is scaled).
size image.Point
config Config
wsize image.Point // window config size before going fullscreen
size image.Point
config Config
wsize image.Point // window config size before going fullscreen or maximized
inCompositor bool // window is moving or being resized

wakeups chan struct{}
}
Expand All @@ -212,7 +215,7 @@ type wlOutput struct {
}

// callbackMap maps Wayland native handles to corresponding Go
// references. It is necessary because the the Wayland client API
// references. It is necessary because the Wayland client API
// forces the use of callbacks and storing pointers to Go values
// in C is forbidden.
var callbackMap sync.Map
Expand Down Expand Up @@ -369,9 +372,8 @@ func (d *wlDisplay) createNativeWindow(options []Option) (*window, error) {
C.xdg_toplevel_add_listener(w.topLvl, &C.gio_xdg_toplevel_listener, unsafe.Pointer(w.surf))

if d.decor != nil {
// Request server side decorations.
w.decor = C.zxdg_decoration_manager_v1_get_toplevel_decoration(d.decor, w.topLvl)
C.zxdg_toplevel_decoration_v1_set_mode(w.decor, C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE)
C.zxdg_toplevel_decoration_v1_add_listener(w.decor, &C.gio_zxdg_toplevel_decoration_v1_listener, unsafe.Pointer(w.surf))
}
w.updateOpaqueRegion()
return w, nil
Expand Down Expand Up @@ -499,6 +501,24 @@ func gio_onToplevelConfigure(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel,
w.size = image.Pt(int(width), int(height))
w.updateOpaqueRegion()
}
w.needAck = true
}

//export gio_onToplevelDecorationConfigure
func gio_onToplevelDecorationConfigure(data unsafe.Pointer, deco *C.struct_zxdg_toplevel_decoration_v1, mode C.uint32_t) {
w := callbackLoad(data).(*window)
decorated := w.config.Decorated
switch mode {
case C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:
w.config.Decorated = false
case C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:
w.config.Decorated = true
}
if decorated != w.config.Decorated {
w.w.Event(ConfigEvent{Config: w.config})
}
w.needAck = true
w.draw(true)
}

//export gio_onOutputMode
Expand Down Expand Up @@ -772,15 +792,22 @@ func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, seria
s := callbackLoad(data).(*wlSeat)
s.serial = serial
w := callbackLoad(unsafe.Pointer(surf)).(*window)
w.seat = s
s.pointerFocus = w
w.setCursor(pointer, serial)
w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)}
}

//export gio_onPointerLeave
func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surface *C.struct_wl_surface) {
func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface) {
w := callbackLoad(unsafe.Pointer(surf)).(*window)
w.seat = nil
s := callbackLoad(data).(*wlSeat)
s.serial = serial
if w.inCompositor {
w.inCompositor = false
w.w.Event(pointer.Event{Type: pointer.Cancel})
}
}

//export gio_onPointerMotion
Expand Down Expand Up @@ -818,6 +845,8 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
case 0:
w.pointerBtns &^= btn
typ = pointer.Release
// Move or resize gestures no longer applies.
w.inCompositor = false
case 1:
w.pointerBtns |= btn
typ = pointer.Press
Expand Down Expand Up @@ -978,6 +1007,9 @@ func (w *window) Configure(options []Option) {
C.xdg_toplevel_set_max_size(w.topLvl, C.int32_t(cnf.MaxSize.X), C.int32_t(cnf.MaxSize.Y))
}
}
if cnf.Decorated != prev.Decorated {
w.config.Decorated = cnf.Decorated
}
if w.config != prev {
w.w.Event(ConfigEvent{Config: w.config})
}
Expand All @@ -992,6 +1024,63 @@ func (w *window) setTitle(prev, cnf Config) {
}
}

func (w *window) Perform(actions system.Action) {
walkActions(actions, func(action system.Action) {
switch action {
case system.ActionMinimize:
w.Configure([]Option{Minimized.Option()})
case system.ActionMaximize:
w.Configure([]Option{Maximized.Option()})
case system.ActionUnmaximize:
w.Configure([]Option{Windowed.Option()})
case system.ActionClose:
w.Close()
case system.ActionMove:
w.move()
default:
w.resize(action)
}
})
}

func (w *window) move() {
if !w.inCompositor && w.seat != nil {
w.inCompositor = true
s := w.seat
C.xdg_toplevel_move(w.topLvl, s.seat, s.serial)
}
}

func (w *window) resize(a system.Action) {
if w.inCompositor || w.seat == nil {
return
}
var edge int
switch a {
case system.ActionResizeNorth:
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_TOP
case system.ActionResizeSouth:
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM
case system.ActionResizeEast:
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_LEFT
case system.ActionResizeWest:
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_RIGHT
case system.ActionResizeNorthWest:
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT
case system.ActionResizeNorthEast:
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT
case system.ActionResizeSouthEast:
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT
case system.ActionResizeSouthWest:
edge = C.XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT
default:
return
}
w.inCompositor = true
s := w.seat
C.xdg_toplevel_resize(w.topLvl, s.seat, s.serial, C.uint32_t(edge))
}

func (w *window) Raise() {
// NB. there is no way for a minimized window to be unminimized.
// https://wayland.app/protocols/xdg-shell#xdg_toplevel:request:set_minimized
Expand Down
4 changes: 4 additions & 0 deletions app/os_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,8 @@ func (w *window) Configure(options []Option) {
metric := configForDPI(dpi)
w.config.apply(metric, options)
windows.SetWindowText(w.hwnd, w.config.Title)
// Decorations are never disabled.
w.config.Decorated = true

switch w.config.Mode {
case Minimized:
Expand Down Expand Up @@ -691,6 +693,8 @@ func (w *window) Close() {
windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0)
}

func (w *window) Perform(system.Action) {}

func (w *window) Raise() {
windows.SetForegroundWindow(w.hwnd)
windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST, 0, 0, 0, 0,
Expand Down
7 changes: 7 additions & 0 deletions app/os_x11.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ func (w *x11Window) Configure(options []Option) {
prev := w.config
cnf := w.config
cnf.apply(w.metric, options)
// Decorations are never disabled.
cnf.Decorated = true

switch cnf.Mode {
case Fullscreen:
Expand Down Expand Up @@ -245,6 +247,9 @@ func (w *x11Window) Configure(options []Option) {
C.XMoveResizeWindow(w.x, w.xw, C.int(x), C.int(y), C.uint(sz.X), C.uint(sz.Y))
}
}
if cnf.Decorated != prev.Decorated {
w.config.Decorated = cnf.Decorated
}
if w.config != prev {
w.w.Event(ConfigEvent{Config: w.config})
}
Expand All @@ -268,6 +273,8 @@ func (w *x11Window) setTitle(prev, cnf Config) {
}
}

func (w *x11Window) Perform(system.Action) {}

func (w *x11Window) Raise() {
var xev C.XEvent
ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
Expand Down
Loading

0 comments on commit 5ce1e98

Please sign in to comment.