app: added support for fullscreen mode

The option field WindowMode allows changing the window mode of an application in either Windowed or Fullscreen.
Only macOS, Windows and X11 platforms are currently supported.

Updates gio#89.

Signed-off-by: pierre <pierre.curto@gmail.com>
This commit is contained in:
pierre
2021-03-23 15:39:25 +01:00
committed by Elias Naur
parent bc2c3db43e
commit 238dd1aa86
7 changed files with 171 additions and 1 deletions
+69
View File
@@ -54,6 +54,23 @@ type MinMaxInfo struct {
PtMaxTrackSize Point PtMaxTrackSize Point
} }
type WindowPlacement struct {
length uint32
flags uint32
showCmd uint32
ptMinPosition Point
ptMaxPosition Point
rcNormalPosition Rect
rcDevice Rect
}
type MonitorInfo struct {
cbSize uint32
Monitor Rect
WorkArea Rect
Flags uint32
}
const ( const (
TRUE = 1 TRUE = 1
@@ -63,6 +80,9 @@ const (
CW_USEDEFAULT = -2147483648 CW_USEDEFAULT = -2147483648
GWL_STYLE = ^(uint32(16) - 1) // -16
HWND_TOPMOST = ^(uint32(1) - 1) // -1
HTCLIENT = 1 HTCLIENT = 1
IDC_ARROW = 32512 IDC_ARROW = 32512
@@ -87,6 +107,12 @@ const (
SW_SHOWDEFAULT = 10 SW_SHOWDEFAULT = 10
SWP_FRAMECHANGED = 0x0020
SWP_NOMOVE = 0x0002
SWP_NOOWNERZORDER = 0x0200
SWP_NOSIZE = 0x0001
SWP_NOZORDER = 0x0004
USER_TIMER_MINIMUM = 0x0000000A USER_TIMER_MINIMUM = 0x0000000A
VK_CONTROL = 0x11 VK_CONTROL = 0x11
@@ -237,10 +263,14 @@ var (
_GetKeyState = user32.NewProc("GetKeyState") _GetKeyState = user32.NewProc("GetKeyState")
_GetMessage = user32.NewProc("GetMessageW") _GetMessage = user32.NewProc("GetMessageW")
_GetMessageTime = user32.NewProc("GetMessageTime") _GetMessageTime = user32.NewProc("GetMessageTime")
_GetMonitorInfo = user32.NewProc("GetMonitorInfoW")
_GetWindowLong = user32.NewProc("GetWindowLongPtrW")
_GetWindowPlacement = user32.NewProc("GetWindowPlacement")
_KillTimer = user32.NewProc("KillTimer") _KillTimer = user32.NewProc("KillTimer")
_LoadCursor = user32.NewProc("LoadCursorW") _LoadCursor = user32.NewProc("LoadCursorW")
_LoadImage = user32.NewProc("LoadImageW") _LoadImage = user32.NewProc("LoadImageW")
_MonitorFromPoint = user32.NewProc("MonitorFromPoint") _MonitorFromPoint = user32.NewProc("MonitorFromPoint")
_MonitorFromWindow = user32.NewProc("MonitorFromWindow")
_MsgWaitForMultipleObjectsEx = user32.NewProc("MsgWaitForMultipleObjectsEx") _MsgWaitForMultipleObjectsEx = user32.NewProc("MsgWaitForMultipleObjectsEx")
_OpenClipboard = user32.NewProc("OpenClipboard") _OpenClipboard = user32.NewProc("OpenClipboard")
_PeekMessage = user32.NewProc("PeekMessageW") _PeekMessage = user32.NewProc("PeekMessageW")
@@ -258,6 +288,9 @@ var (
_SetFocus = user32.NewProc("SetFocus") _SetFocus = user32.NewProc("SetFocus")
_SetProcessDPIAware = user32.NewProc("SetProcessDPIAware") _SetProcessDPIAware = user32.NewProc("SetProcessDPIAware")
_SetTimer = user32.NewProc("SetTimer") _SetTimer = user32.NewProc("SetTimer")
_SetWindowLong = user32.NewProc("SetWindowLongPtrW")
_SetWindowPos = user32.NewProc("SetWindowPos")
_SetWindowPlacement = user32.NewProc("SetWindowPlacement")
_TranslateMessage = user32.NewProc("TranslateMessage") _TranslateMessage = user32.NewProc("TranslateMessage")
_UnregisterClass = user32.NewProc("UnregisterClassW") _UnregisterClass = user32.NewProc("UnregisterClassW")
_UpdateWindow = user32.NewProc("UpdateWindow") _UpdateWindow = user32.NewProc("UpdateWindow")
@@ -417,6 +450,42 @@ func GetWindowDPI(hwnd syscall.Handle) int {
} }
} }
func GetWindowPlacement(hwnd syscall.Handle) *WindowPlacement {
var wp WindowPlacement
wp.length = uint32(unsafe.Sizeof(wp))
_GetWindowPlacement.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&wp)))
return &wp
}
func GetMonitorInfo(hwnd syscall.Handle) MonitorInfo {
var mi MonitorInfo
mi.cbSize = uint32(unsafe.Sizeof(mi))
v, _, _ := _MonitorFromWindow.Call(uintptr(hwnd), MONITOR_DEFAULTTOPRIMARY)
_GetMonitorInfo.Call(v, uintptr(unsafe.Pointer(&mi)))
return mi
}
func GetWindowLong(hwnd syscall.Handle) (style uintptr) {
style, _, _ = _GetWindowLong.Call(uintptr(hwnd), uintptr(GWL_STYLE))
return
}
func SetWindowLong(hwnd syscall.Handle, idx uint32, style uintptr) {
_SetWindowLong.Call(uintptr(hwnd), uintptr(idx), style)
}
func SetWindowPlacement(hwnd syscall.Handle, wp *WindowPlacement) {
_SetWindowPlacement.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wp)))
}
func SetWindowPos(hwnd syscall.Handle, hwndInsertAfter uint32, x, y, dx, dy int32, style uintptr) {
_SetWindowPos.Call(uintptr(hwnd), uintptr(hwndInsertAfter),
uintptr(x), uintptr(y),
uintptr(dx), uintptr(dy),
style,
)
}
func GlobalAlloc(size int) (syscall.Handle, error) { func GlobalAlloc(size int) (syscall.Handle, error) {
r, _, err := _GlobalAlloc.Call(GHND, uintptr(size)) r, _, err := _GlobalAlloc.Call(GHND, uintptr(size))
if r == 0 { if r == 0 {
+13
View File
@@ -41,6 +41,7 @@ __attribute__ ((visibility ("hidden"))) CGFloat gio_getScreenBackingScale(void);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_readClipboard(void); __attribute__ ((visibility ("hidden"))) CFTypeRef gio_readClipboard(void);
__attribute__ ((visibility ("hidden"))) void gio_writeClipboard(unichar *chars, NSUInteger length); __attribute__ ((visibility ("hidden"))) void gio_writeClipboard(unichar *chars, NSUInteger length);
__attribute__ ((visibility ("hidden"))) void gio_setNeedsDisplay(CFTypeRef viewRef); __attribute__ ((visibility ("hidden"))) void gio_setNeedsDisplay(CFTypeRef viewRef);
__attribute__ ((visibility ("hidden"))) void gio_toggleFullScreen(CFTypeRef windowRef);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight); __attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
__attribute__ ((visibility ("hidden"))) void gio_makeKeyAndOrderFront(CFTypeRef windowRef); __attribute__ ((visibility ("hidden"))) void gio_makeKeyAndOrderFront(CFTypeRef windowRef);
__attribute__ ((visibility ("hidden"))) NSPoint gio_cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft); __attribute__ ((visibility ("hidden"))) NSPoint gio_cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft);
@@ -62,6 +63,7 @@ type window struct {
cursor pointer.CursorName cursor pointer.CursorName
scale float32 scale float32
mode WindowMode
} }
// viewMap is the mapping from Cocoa NSViews to Go windows. // viewMap is the mapping from Cocoa NSViews to Go windows.
@@ -124,6 +126,16 @@ func (w *window) WriteClipboard(s string) {
}) })
} }
func (w *window) SetWindowMode(mode WindowMode) {
switch mode {
case w.mode:
return
case Fullscreen:
C.gio_toggleFullScreen(w.window)
}
w.mode = mode
}
func (w *window) SetCursor(name pointer.CursorName) { func (w *window) SetCursor(name pointer.CursorName) {
w.cursor = windowSetCursor(w.cursor, name) w.cursor = windowSetCursor(w.cursor, name)
} }
@@ -353,6 +365,7 @@ func NewWindow(win Callbacks, opts *Options) error {
} }
nextTopLeft = C.gio_cascadeTopLeftFromPoint(w.window, nextTopLeft) nextTopLeft = C.gio_cascadeTopLeftFromPoint(w.window, nextTopLeft)
C.gio_makeKeyAndOrderFront(w.window) C.gio_makeKeyAndOrderFront(w.window)
w.SetWindowMode(opts.WindowMode)
}) })
return <-errch return <-errch
} }
+5
View File
@@ -167,6 +167,11 @@ void gio_makeKeyAndOrderFront(CFTypeRef windowRef) {
[window makeKeyAndOrderFront:nil]; [window makeKeyAndOrderFront:nil];
} }
void gio_toggleFullScreen(CFTypeRef windowRef) {
NSWindow *window = (__bridge NSWindow *)windowRef;
[window toggleFullScreen:nil];
}
CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight) { CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight) {
@autoreleasepool { @autoreleasepool {
NSRect rect = NSMakeRect(0, 0, width, height); NSRect rect = NSMakeRect(0, 0, width, height);
+36
View File
@@ -52,6 +52,9 @@ type window struct {
cursorIn bool cursorIn bool
cursor syscall.Handle cursor syscall.Handle
// placement saves the previous window position when in full screen mode.
placement *windows.WindowPlacement
mu sync.Mutex mu sync.Mutex
animating bool animating bool
@@ -119,6 +122,7 @@ func NewWindow(window Callbacks, opts *Options) error {
// Since the window class for the cursor is null, // Since the window class for the cursor is null,
// set it here to show the cursor. // set it here to show the cursor.
w.SetCursor(pointer.CursorDefault) w.SetCursor(pointer.CursorDefault)
w.SetWindowMode(opts.WindowMode)
if err := w.loop(); err != nil { if err := w.loop(); err != nil {
panic(err) panic(err)
} }
@@ -524,6 +528,38 @@ func (w *window) readClipboard() error {
return nil return nil
} }
func (w *window) SetWindowMode(mode WindowMode) {
// https://devblogs.microsoft.com/oldnewthing/20100412-00/?p=14353
switch mode {
case Windowed:
if w.placement == nil {
return
}
windows.SetWindowPlacement(w.hwnd, w.placement)
w.placement = nil
style := windows.GetWindowLong(w.hwnd)
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style|windows.WS_OVERLAPPEDWINDOW)
windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST,
0, 0, 0, 0,
windows.SWP_NOOWNERZORDER|windows.SWP_FRAMECHANGED,
)
case Fullscreen:
if w.placement != nil {
return
}
w.placement = windows.GetWindowPlacement(w.hwnd)
style := windows.GetWindowLong(w.hwnd)
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style&^windows.WS_OVERLAPPEDWINDOW)
mi := windows.GetMonitorInfo(w.hwnd)
windows.SetWindowPos(w.hwnd, 0,
mi.Monitor.Left, mi.Monitor.Top,
mi.Monitor.Right-mi.Monitor.Left,
mi.Monitor.Bottom-mi.Monitor.Top,
windows.SWP_NOOWNERZORDER|windows.SWP_FRAMECHANGED,
)
}
}
func (w *window) WriteClipboard(s string) { func (w *window) WriteClipboard(s string) {
w.writeClipboard(s) w.writeClipboard(s)
} }
+24 -1
View File
@@ -71,6 +71,12 @@ type x11Window struct {
atom C.Atom atom C.Atom
// "GTK_TEXT_BUFFER_CONTENTS" // "GTK_TEXT_BUFFER_CONTENTS"
gtk_text_buffer_contents C.Atom gtk_text_buffer_contents C.Atom
// "_NET_WM_NAME"
wmName C.Atom
// "_NET_WM_STATE"
wmState C.Atom
// _NET_WM_STATE_FULLSCREEN"
wmStateFullscreen C.Atom
} }
stage system.Stage stage system.Stage
cfg unit.Metric cfg unit.Metric
@@ -141,6 +147,18 @@ func (w *x11Window) SetCursor(name pointer.CursorName) {
C.XDefineCursor(w.x, w.xw, c) C.XDefineCursor(w.x, w.xw, c)
} }
func (w *x11Window) SetWindowMode(mode WindowMode) {
switch mode {
case Windowed:
C.XDeleteProperty(w.x, w.xw, w.atoms.wmStateFullscreen)
case Fullscreen:
C.XChangeProperty(w.x, w.xw, w.atoms.wmState, C.XA_ATOM,
32, C.PropModeReplace,
(*C.uchar)(unsafe.Pointer(&w.atoms.wmStateFullscreen)), 1,
)
}
}
func (w *x11Window) ShowTextInput(show bool) {} func (w *x11Window) ShowTextInput(show bool) {}
// Close the window. // Close the window.
@@ -612,6 +630,9 @@ func newX11Window(gioWin Callbacks, opts *Options) error {
w.atoms.clipboardContent = w.atom("CLIPBOARD_CONTENT", false) w.atoms.clipboardContent = w.atom("CLIPBOARD_CONTENT", false)
w.atoms.atom = w.atom("ATOM", false) w.atoms.atom = w.atom("ATOM", false)
w.atoms.targets = w.atom("TARGETS", false) w.atoms.targets = w.atom("TARGETS", false)
w.atoms.wmName = w.atom("_NET_WM_NAME", false)
w.atoms.wmState = w.atom("_NET_WM_STATE", false)
w.atoms.wmStateFullscreen = w.atom("_NET_WM_STATE_FULLSCREEN", false)
// set the name // set the name
ctitle := C.CString(opts.Title) ctitle := C.CString(opts.Title)
@@ -625,11 +646,13 @@ func newX11Window(gioWin Callbacks, opts *Options) error {
format: 8, format: 8,
nitems: C.ulong(len(opts.Title)), nitems: C.ulong(len(opts.Title)),
}, },
w.atom("_NET_WM_NAME", false)) w.atoms.wmName)
// extensions // extensions
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1) C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
w.SetWindowMode(opts.WindowMode)
// make the window visible on the screen // make the window visible on the screen
C.XMapWindow(dpy, win) C.XMapWindow(dpy, win)
+8
View File
@@ -19,8 +19,16 @@ type Options struct {
MinWidth, MinHeight unit.Value MinWidth, MinHeight unit.Value
MaxWidth, MaxHeight unit.Value MaxWidth, MaxHeight unit.Value
Title string Title string
WindowMode WindowMode
} }
type WindowMode uint8
const (
Windowed WindowMode = iota
Fullscreen
)
type FrameEvent struct { type FrameEvent struct {
system.FrameEvent system.FrameEvent
+16
View File
@@ -444,6 +444,22 @@ func (q *queue) Events(k event.Tag) []event.Event {
return q.q.Events(k) return q.q.Events(k)
} }
const (
// Windowed is the normal window mode with OS specific window decorations.
Windowed = wm.Windowed
// Fullscreen is the full screen window mode.
Fullscreen = wm.Fullscreen
)
// WindowMode sets the window mode.
//
// Supported platforms are macOS, X11 and Windows.
func WindowMode(mode wm.WindowMode) Option {
return func(opts *wm.Options) {
opts.WindowMode = mode
}
}
// Title sets the title of the wm. // Title sets the title of the wm.
func Title(t string) Option { func Title(t string) Option {
return func(opts *wm.Options) { return func(opts *wm.Options) {