Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions v3/internal/runtime/desktop/@wailsio/runtime/src/screens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ const call = newRuntimeCaller(objectNames.Screens);
const getAll = 0;
const getPrimary = 1;
const getCurrent = 2;
const getByID = 3;
const getByIndex = 4;

/**
* Gets all screens.
Expand Down Expand Up @@ -86,3 +88,23 @@ export function GetPrimary(): Promise<Screen> {
export function GetCurrent(): Promise<Screen> {
return call(getCurrent);
}

/**
* Gets a screen by its unique display ID.
*
* @param id - The unique identifier of the screen.
* @returns A promise that resolves to the matching Screen.
*/
export function GetByID(id: string): Promise<Screen> {
return call(getByID, { id });
}

/**
* Gets a screen by its index in the screen list.
*
* @param index - The zero-based index of the screen.
* @returns A promise that resolves to the matching Screen.
*/
export function GetByIndex(index: number): Promise<Screen> {
return call(getByIndex, { index });
}
10 changes: 10 additions & 0 deletions v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const ZoomResetMethod = 48;
const SnapAssistMethod = 49;
const FilesDropped = 50;
const PrintMethod = 51;
const SetScreenMethod = 52;

/**
* Finds the nearest drop target element by walking up the DOM tree.
Expand Down Expand Up @@ -671,6 +672,15 @@ class Window {
cleanupNativeDrag();
}

/**
* Moves the window to the center of the specified screen's work area.
*
* @param screenID - The ID of the target screen.
*/
SetScreen(screenID: string): Promise<void> {
return this[callerSym](SetScreenMethod, { screenID });
}

/* Triggers Windows 11 Snap Assist feature (Windows only).
* This is equivalent to pressing Win+Z and shows snap layout options.
*/
Expand Down
12 changes: 12 additions & 0 deletions v3/pkg/application/application_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,18 @@ func (m *macosApp) run() error {
)
C.setActivationPolicy(C.int(m.parent.options.Mac.ActivationPolicy))
C.activateIgnoringOtherApps()
if err := m.processAndCacheScreens(); err != nil {
m.parent.handleError(err)
}
},
)
// Refresh screen cache when display configuration changes
m.parent.Event.OnApplicationEvent(
events.Mac.ApplicationDidChangeScreenParameters,
func(*ApplicationEvent) {
if err := m.processAndCacheScreens(); err != nil {
m.parent.handleError(err)
}
},
)
m.setupCommonEvents()
Expand Down
4 changes: 3 additions & 1 deletion v3/pkg/application/application_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,9 @@ func (a *linuxApp) run() error {
}

a.parent.Event.OnApplicationEvent(events.Linux.ApplicationStartup, func(evt *ApplicationEvent) {
// TODO: What should happen here?
if err := a.processAndCacheScreens(); err != nil {
a.parent.handleError(err)
}
})
a.setupCommonEvents()
a.monitorThemeChanges()
Expand Down
5 changes: 5 additions & 0 deletions v3/pkg/application/application_linux_gtk4.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ func (a *linuxApp) name() string {
}

func (a *linuxApp) run() error {
a.parent.Event.OnApplicationEvent(events.Linux.ApplicationStartup, func(evt *ApplicationEvent) {
if err := a.processAndCacheScreens(); err != nil {
a.parent.handleError(err)
}
})
return appRun(a.application)
}

Expand Down
1 change: 1 addition & 0 deletions v3/pkg/application/application_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@ func (w *serverWebviewWindow) bounds() Rect { retu
func (w *serverWebviewWindow) setBounds(bounds Rect) {}
func (w *serverWebviewWindow) position() (int, int) { return 0, 0 }
func (w *serverWebviewWindow) setPosition(x int, y int) {}
func (w *serverWebviewWindow) centerOnScreen(_ *Screen) {}
func (w *serverWebviewWindow) relativePosition() (int, int) { return 0, 0 }
func (w *serverWebviewWindow) setRelativePosition(x int, y int) {}
func (w *serverWebviewWindow) flash(enabled bool) {}
Expand Down
149 changes: 104 additions & 45 deletions v3/pkg/application/linux_cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -970,50 +970,71 @@ func menuRadioItemNew(group *GSList, label string) pointer {

func getScreenByIndex(display *C.struct__GdkDisplay, index int) *Screen {
monitor := C.gdk_display_get_monitor(display, C.int(index))
// TODO: Do we need to update Screen to contain current info?
// currentMonitor := C.gdk_display_get_monitor_at_window(display, window)

var geometry C.GdkRectangle
C.gdk_monitor_get_geometry(monitor, &geometry)
primary := false
if C.gdk_monitor_is_primary(monitor) == 1 {
primary = true
}

var workarea C.GdkRectangle
C.gdk_monitor_get_workarea(monitor, &workarea)

scaleFactor := float32(C.gdk_monitor_get_scale_factor(monitor))
primary := C.gdk_monitor_is_primary(monitor) == 1
name := C.gdk_monitor_get_model(monitor)

x := int(geometry.x)
y := int(geometry.y)
width := int(geometry.width)
height := int(geometry.height)

pX := int(float32(x) * scaleFactor)
pY := int(float32(y) * scaleFactor)
pWidth := int(float32(width) * scaleFactor)
pHeight := int(float32(height) * scaleFactor)

waX := int(workarea.x)
waY := int(workarea.y)
waWidth := int(workarea.width)
waHeight := int(workarea.height)

pwaX := int(float32(waX) * scaleFactor)
pwaY := int(float32(waY) * scaleFactor)
pwaWidth := int(float32(waWidth) * scaleFactor)
pwaHeight := int(float32(waHeight) * scaleFactor)

return &Screen{
ID: fmt.Sprintf("%d", index),
Name: C.GoString(name),
IsPrimary: primary,
ScaleFactor: float32(C.gdk_monitor_get_scale_factor(monitor)),
X: int(geometry.x),
Y: int(geometry.y),
ScaleFactor: scaleFactor,
X: x,
Y: y,
Size: Size{
Height: int(geometry.height),
Width: int(geometry.width),
Height: height,
Width: width,
},
Bounds: Rect{
X: int(geometry.x),
Y: int(geometry.y),
Height: int(geometry.height),
Width: int(geometry.width),
X: x,
Y: y,
Height: height,
Width: width,
},
PhysicalBounds: Rect{
X: int(geometry.x),
Y: int(geometry.y),
Height: int(geometry.height),
Width: int(geometry.width),
X: pX,
Y: pY,
Height: pHeight,
Width: pWidth,
},
WorkArea: Rect{
X: int(geometry.x),
Y: int(geometry.y),
Height: int(geometry.height),
Width: int(geometry.width),
X: waX,
Y: waY,
Height: waHeight,
Width: waWidth,
},
PhysicalWorkArea: Rect{
X: int(geometry.x),
Y: int(geometry.y),
Height: int(geometry.height),
Width: int(geometry.width),
X: pwaX,
Y: pwaY,
Height: pwaHeight,
Width: pwaWidth,
},
Rotation: 0.0,
}
Expand Down Expand Up @@ -1489,29 +1510,67 @@ func (w *linuxWebviewWindow) setBackgroundColour(colour RGBA) {

func getPrimaryScreen() (*Screen, error) {
display := C.gdk_display_get_default()
monitor := C.gdk_display_get_primary_monitor(display)
geometry := C.GdkRectangle{}
C.gdk_monitor_get_geometry(monitor, &geometry)
scaleFactor := int(C.gdk_monitor_get_scale_factor(monitor))
// get the name for the screen
name := C.gdk_monitor_get_model(monitor)
primaryMonitor := C.gdk_display_get_primary_monitor(display)

// Find the index of the primary monitor so the ID matches getScreenByIndex's contract.
primaryIndex := 0
count := int(C.gdk_display_get_n_monitors(display))
for i := 0; i < count; i++ {
if C.gdk_display_get_monitor(display, C.int(i)) == primaryMonitor {
primaryIndex = i
break
}
}

var geometry C.GdkRectangle
C.gdk_monitor_get_geometry(primaryMonitor, &geometry)

var workarea C.GdkRectangle
C.gdk_monitor_get_workarea(primaryMonitor, &workarea)

scaleFactor := float32(C.gdk_monitor_get_scale_factor(primaryMonitor))
name := C.gdk_monitor_get_model(primaryMonitor)

x := int(geometry.x)
y := int(geometry.y)
width := int(geometry.width)
height := int(geometry.height)

return &Screen{
ID: "0",
Name: C.GoString(name),
IsPrimary: true,
X: int(geometry.x),
Y: int(geometry.y),
ID: fmt.Sprintf("%d", primaryIndex),
Name: C.GoString(name),
IsPrimary: true,
ScaleFactor: scaleFactor,
X: x,
Y: y,
Size: Size{
Height: int(geometry.height),
Width: int(geometry.width),
Height: height,
Width: width,
},
Bounds: Rect{
X: int(geometry.x),
Y: int(geometry.y),
Height: int(geometry.height),
Width: int(geometry.width),
X: x,
Y: y,
Height: height,
Width: width,
},
PhysicalBounds: Rect{
X: int(float32(x) * scaleFactor),
Y: int(float32(y) * scaleFactor),
Height: int(float32(height) * scaleFactor),
Width: int(float32(width) * scaleFactor),
},
WorkArea: Rect{
X: int(workarea.x),
Y: int(workarea.y),
Height: int(workarea.height),
Width: int(workarea.width),
},
PhysicalWorkArea: Rect{
X: int(float32(workarea.x) * scaleFactor),
Y: int(float32(workarea.y) * scaleFactor),
Height: int(float32(workarea.height) * scaleFactor),
Width: int(float32(workarea.width) * scaleFactor),
},
ScaleFactor: float32(scaleFactor),
}, nil
Comment thread
atterpac marked this conversation as resolved.
}

Expand Down
Loading
Loading