diff --git a/app/internal/windows/windows.go b/app/internal/windows/windows.go index 17ce6bb1..1601172e 100644 --- a/app/internal/windows/windows.go +++ b/app/internal/windows/windows.go @@ -54,6 +54,23 @@ type MinMaxInfo struct { 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 ( TRUE = 1 @@ -63,6 +80,9 @@ const ( CW_USEDEFAULT = -2147483648 + GWL_STYLE = ^(uint32(16) - 1) // -16 + HWND_TOPMOST = ^(uint32(1) - 1) // -1 + HTCLIENT = 1 IDC_ARROW = 32512 @@ -87,6 +107,12 @@ const ( SW_SHOWDEFAULT = 10 + SWP_FRAMECHANGED = 0x0020 + SWP_NOMOVE = 0x0002 + SWP_NOOWNERZORDER = 0x0200 + SWP_NOSIZE = 0x0001 + SWP_NOZORDER = 0x0004 + USER_TIMER_MINIMUM = 0x0000000A VK_CONTROL = 0x11 @@ -237,10 +263,14 @@ var ( _GetKeyState = user32.NewProc("GetKeyState") _GetMessage = user32.NewProc("GetMessageW") _GetMessageTime = user32.NewProc("GetMessageTime") + _GetMonitorInfo = user32.NewProc("GetMonitorInfoW") + _GetWindowLong = user32.NewProc("GetWindowLongPtrW") + _GetWindowPlacement = user32.NewProc("GetWindowPlacement") _KillTimer = user32.NewProc("KillTimer") _LoadCursor = user32.NewProc("LoadCursorW") _LoadImage = user32.NewProc("LoadImageW") _MonitorFromPoint = user32.NewProc("MonitorFromPoint") + _MonitorFromWindow = user32.NewProc("MonitorFromWindow") _MsgWaitForMultipleObjectsEx = user32.NewProc("MsgWaitForMultipleObjectsEx") _OpenClipboard = user32.NewProc("OpenClipboard") _PeekMessage = user32.NewProc("PeekMessageW") @@ -258,6 +288,9 @@ var ( _SetFocus = user32.NewProc("SetFocus") _SetProcessDPIAware = user32.NewProc("SetProcessDPIAware") _SetTimer = user32.NewProc("SetTimer") + _SetWindowLong = user32.NewProc("SetWindowLongPtrW") + _SetWindowPos = user32.NewProc("SetWindowPos") + _SetWindowPlacement = user32.NewProc("SetWindowPlacement") _TranslateMessage = user32.NewProc("TranslateMessage") _UnregisterClass = user32.NewProc("UnregisterClassW") _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) { r, _, err := _GlobalAlloc.Call(GHND, uintptr(size)) if r == 0 { diff --git a/app/internal/wm/os_macos.go b/app/internal/wm/os_macos.go index 09d9d297..68ead7b0 100644 --- a/app/internal/wm/os_macos.go +++ b/app/internal/wm/os_macos.go @@ -41,6 +41,7 @@ __attribute__ ((visibility ("hidden"))) CGFloat gio_getScreenBackingScale(void); __attribute__ ((visibility ("hidden"))) CFTypeRef gio_readClipboard(void); __attribute__ ((visibility ("hidden"))) void gio_writeClipboard(unichar *chars, NSUInteger length); __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"))) void gio_makeKeyAndOrderFront(CFTypeRef windowRef); __attribute__ ((visibility ("hidden"))) NSPoint gio_cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft); @@ -62,6 +63,7 @@ type window struct { cursor pointer.CursorName scale float32 + mode WindowMode } // 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) { w.cursor = windowSetCursor(w.cursor, name) } @@ -353,6 +365,7 @@ func NewWindow(win Callbacks, opts *Options) error { } nextTopLeft = C.gio_cascadeTopLeftFromPoint(w.window, nextTopLeft) C.gio_makeKeyAndOrderFront(w.window) + w.SetWindowMode(opts.WindowMode) }) return <-errch } diff --git a/app/internal/wm/os_macos.m b/app/internal/wm/os_macos.m index fa5a98e0..24c906cb 100644 --- a/app/internal/wm/os_macos.m +++ b/app/internal/wm/os_macos.m @@ -167,6 +167,11 @@ void gio_makeKeyAndOrderFront(CFTypeRef windowRef) { [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) { @autoreleasepool { NSRect rect = NSMakeRect(0, 0, width, height); diff --git a/app/internal/wm/os_windows.go b/app/internal/wm/os_windows.go index a59fdd8e..f50fa43b 100644 --- a/app/internal/wm/os_windows.go +++ b/app/internal/wm/os_windows.go @@ -52,6 +52,9 @@ type window struct { cursorIn bool cursor syscall.Handle + // placement saves the previous window position when in full screen mode. + placement *windows.WindowPlacement + mu sync.Mutex animating bool @@ -119,6 +122,7 @@ func NewWindow(window Callbacks, opts *Options) error { // Since the window class for the cursor is null, // set it here to show the cursor. w.SetCursor(pointer.CursorDefault) + w.SetWindowMode(opts.WindowMode) if err := w.loop(); err != nil { panic(err) } @@ -524,6 +528,38 @@ func (w *window) readClipboard() error { 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) { w.writeClipboard(s) } diff --git a/app/internal/wm/os_x11.go b/app/internal/wm/os_x11.go index edb568e0..a5a25c66 100644 --- a/app/internal/wm/os_x11.go +++ b/app/internal/wm/os_x11.go @@ -71,6 +71,12 @@ type x11Window struct { atom C.Atom // "GTK_TEXT_BUFFER_CONTENTS" 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 cfg unit.Metric @@ -141,6 +147,18 @@ func (w *x11Window) SetCursor(name pointer.CursorName) { 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) {} // Close the window. @@ -612,6 +630,9 @@ func newX11Window(gioWin Callbacks, opts *Options) error { w.atoms.clipboardContent = w.atom("CLIPBOARD_CONTENT", false) w.atoms.atom = w.atom("ATOM", 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 ctitle := C.CString(opts.Title) @@ -625,11 +646,13 @@ func newX11Window(gioWin Callbacks, opts *Options) error { format: 8, nitems: C.ulong(len(opts.Title)), }, - w.atom("_NET_WM_NAME", false)) + w.atoms.wmName) // extensions C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1) + w.SetWindowMode(opts.WindowMode) + // make the window visible on the screen C.XMapWindow(dpy, win) diff --git a/app/internal/wm/window.go b/app/internal/wm/window.go index 42d1df75..b33a1b66 100644 --- a/app/internal/wm/window.go +++ b/app/internal/wm/window.go @@ -19,8 +19,16 @@ type Options struct { MinWidth, MinHeight unit.Value MaxWidth, MaxHeight unit.Value Title string + WindowMode WindowMode } +type WindowMode uint8 + +const ( + Windowed WindowMode = iota + Fullscreen +) + type FrameEvent struct { system.FrameEvent diff --git a/app/window.go b/app/window.go index df8a0179..2de6c1ef 100644 --- a/app/window.go +++ b/app/window.go @@ -444,6 +444,22 @@ func (q *queue) Events(k event.Tag) []event.Event { 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. func Title(t string) Option { return func(opts *wm.Options) {