From ad1d104d85daddffa334d4ba6e3b8abf77a577b3 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Wed, 28 Dec 2022 14:07:13 +1100 Subject: [PATCH] Add screen support Co-authored-by: Oleg Gulevskyy <43781031+oleggulevskyy@users.noreply.github.com> --- exp/examples/window/main.go | 33 +++++- exp/pkg/application/application.go | 8 ++ exp/pkg/application/screen.go | 26 +++++ exp/pkg/application/screen_darwin.go | 151 +++++++++++++++++++++++++++ exp/pkg/application/window.go | 8 ++ exp/pkg/application/window_darwin.go | 4 + 6 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 exp/pkg/application/screen.go create mode 100644 exp/pkg/application/screen_darwin.go diff --git a/exp/examples/window/main.go b/exp/examples/window/main.go index b6cf12856..6c704f7b7 100644 --- a/exp/examples/window/main.go +++ b/exp/examples/window/main.go @@ -2,6 +2,7 @@ package main import ( _ "embed" + "fmt" "log" "math/rand" "strconv" @@ -169,7 +170,37 @@ func main() { w.SetURL("https://wails.io") }) }) - + stateMenu.Add("Get Primary Screen").OnClick(func(ctx *application.Context) { + screen, err := app.GetPrimaryScreen() + if err != nil { + app.NewErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show() + return + } + msg := fmt.Sprintf("Screen: %+v", screen) + app.NewInfoDialog().SetTitle("Primary Screen").SetMessage(msg).Show() + }) + stateMenu.Add("Get Screens").OnClick(func(ctx *application.Context) { + screens, err := app.GetScreens() + if err != nil { + app.NewErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show() + return + } + for _, screen := range screens { + msg := fmt.Sprintf("Screen: %+v", screen) + app.NewInfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() + } + }) + stateMenu.Add("Get Screen for Window").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.Window) { + screen, err := w.GetScreen() + if err != nil { + app.NewErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show() + return + } + msg := fmt.Sprintf("Screen: %+v", screen) + app.NewInfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() + }) + }) app.NewWindow() app.SetMenu(menu) diff --git a/exp/pkg/application/application.go b/exp/pkg/application/application.go index b556580bb..9ee8608b0 100644 --- a/exp/pkg/application/application.go +++ b/exp/pkg/application/application.go @@ -362,6 +362,14 @@ func (a *App) NewSaveFileDialog() *SaveFileDialog { return newSaveFileDialog() } +func (a *App) GetPrimaryScreen() (*Screen, error) { + return getPrimaryScreen() +} + +func (a *App) GetScreens() ([]*Screen, error) { + return getScreens() +} + func (a *App) Clipboard() *Clipboard { if a.clipboard == nil { a.clipboard = newClipboard() diff --git a/exp/pkg/application/screen.go b/exp/pkg/application/screen.go new file mode 100644 index 000000000..b3712c9df --- /dev/null +++ b/exp/pkg/application/screen.go @@ -0,0 +1,26 @@ +package application + +type Screen struct { + ID string // A unique identifier for the display + Name string // The name of the display + Scale float32 // The scale factor of the display + X int // The x-coordinate of the top-left corner of the rectangle + Y int // The y-coordinate of the top-left corner of the rectangle + Size Size // The size of the display + Bounds Rect // The bounds of the display + WorkArea Rect // The work area of the display + IsPrimary bool // Whether this is the primary display + Rotation float32 // The rotation of the display +} + +type Rect struct { + X int + Y int + Width int + Height int +} + +type Size struct { + Width int + Height int +} diff --git a/exp/pkg/application/screen_darwin.go b/exp/pkg/application/screen_darwin.go new file mode 100644 index 000000000..8ca6ca15b --- /dev/null +++ b/exp/pkg/application/screen_darwin.go @@ -0,0 +1,151 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit -framework AppKit +#import +#import +#import +#import +#include + +typedef struct Screen { + const char* id; + const char* name; + int p_width; + int p_height; + int width; + int height; + int x; + int y; + int w_width; + int w_height; + int w_x; + int w_y; + float scale; + double rotation; + bool isPrimary; +} Screen; + + +int GetNumScreens(){ + return [[NSScreen screens] count]; +} + +Screen processScreen(NSScreen* screen){ + Screen returnScreen; + returnScreen.scale = screen.backingScaleFactor; + + // screen bounds + returnScreen.height = screen.frame.size.height; + returnScreen.width = screen.frame.size.width; + returnScreen.x = screen.frame.origin.x; + returnScreen.y = screen.frame.origin.y; + + // work area + NSRect workArea = [screen visibleFrame]; + returnScreen.w_height = workArea.size.height; + returnScreen.w_width = workArea.size.width; + returnScreen.w_x = workArea.origin.x; + returnScreen.w_y = workArea.origin.y; + + + // adapted from https://stackoverflow.com/a/1237490/4188138 + NSDictionary* screenDictionary = [screen deviceDescription]; + NSNumber* screenID = [screenDictionary objectForKey:@"NSScreenNumber"]; + CGDirectDisplayID displayID = [screenID unsignedIntValue]; + returnScreen.id = [[NSString stringWithFormat:@"%d", displayID] UTF8String]; + + // Get physical monitor size + NSValue *sizeValue = [screenDictionary objectForKey:@"NSDeviceSize"]; + NSSize physicalSize = sizeValue.sizeValue; + returnScreen.p_height = physicalSize.height; + returnScreen.p_width = physicalSize.width; + + // Get the rotation + double rotation = CGDisplayRotation(displayID); + returnScreen.rotation = rotation; + + if( @available(macOS 10.15, *) ){ + returnScreen.name = [screen.localizedName UTF8String]; + } + + return returnScreen; +} + +// Get primary screen +Screen GetPrimaryScreen(){ + // Get primary screen + NSScreen *mainScreen = [NSScreen mainScreen]; + return processScreen(mainScreen); +} + +Screen* getAllScreens() { + NSArray *screens = [NSScreen screens]; + Screen* returnScreens = malloc(sizeof(Screen) * screens.count); + for (int i = 0; i < screens.count; i++) { + NSScreen* screen = [screens objectAtIndex:i]; + returnScreens[i] = processScreen(screen); + } + return returnScreens; +} + +Screen getScreenForWindow(void* window){ + NSScreen* screen = ((NSWindow*)window).screen; + return processScreen(screen); +} + +*/ +import "C" +import "unsafe" + +func cScreenToScreen(screen C.Screen) *Screen { + + return &Screen{ + Size: Size{ + Width: int(screen.p_width), + Height: int(screen.p_height), + }, + Bounds: Rect{ + X: int(screen.x), + Y: int(screen.y), + Height: int(screen.height), + Width: int(screen.width), + }, + WorkArea: Rect{ + X: int(screen.w_x), + Y: int(screen.w_y), + Height: int(screen.w_height), + Width: int(screen.w_width), + }, + Scale: float32(screen.scale), + ID: C.GoString(screen.id), + Name: C.GoString(screen.name), + IsPrimary: bool(screen.isPrimary), + Rotation: float32(screen.rotation), + } +} + +func getPrimaryScreen() (*Screen, error) { + cScreen := C.GetPrimaryScreen() + return cScreenToScreen(cScreen), nil +} + +func getScreens() ([]*Screen, error) { + cScreens := C.getAllScreens() + defer C.free(unsafe.Pointer(cScreens)) + numScreens := int(C.GetNumScreens()) + displays := make([]*Screen, numScreens) + cScreenHeaders := (*[1 << 30]C.Screen)(unsafe.Pointer(cScreens))[:numScreens:numScreens] + for i := 0; i < numScreens; i++ { + displays[i] = cScreenToScreen(cScreenHeaders[i]) + } + return displays, nil +} + +func getScreenForWindow(window *macosWindow) (*Screen, error) { + cScreen := C.getScreenForWindow(window.nsWindow) + return cScreenToScreen(cScreen), nil +} diff --git a/exp/pkg/application/window.go b/exp/pkg/application/window.go index 6198933d6..d765179c1 100644 --- a/exp/pkg/application/window.go +++ b/exp/pkg/application/window.go @@ -52,6 +52,7 @@ type ( setFullscreenButtonEnabled(enabled bool) show() hide() + getScreen() (*Screen, error) } ) @@ -545,3 +546,10 @@ func (w *Window) enableSizeConstraints() { w.SetMinSize(w.options.MinWidth, w.options.MinHeight) w.SetMaxSize(w.options.MaxWidth, w.options.MaxHeight) } + +func (w *Window) GetScreen() (*Screen, error) { + if w.impl == nil { + return nil, nil + } + return w.impl.getScreen() +} diff --git a/exp/pkg/application/window_darwin.go b/exp/pkg/application/window_darwin.go index f29a05635..e3cddf6c0 100644 --- a/exp/pkg/application/window_darwin.go +++ b/exp/pkg/application/window_darwin.go @@ -691,6 +691,10 @@ type macosWindow struct { parent *Window } +func (w *macosWindow) getScreen() (*Screen, error) { + return getScreenForWindow(w) +} + func (w *macosWindow) show() { C.windowShow(w.nsWindow) }