Merge branch 'v3-alpha' into fix/security-issues-bundle

This commit is contained in:
Lea Anthony 2026-02-07 18:49:28 +11:00 committed by GitHub
commit 8b619bb27a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 351 additions and 27 deletions

View file

@ -124,7 +124,7 @@ jobs:
uses: awalsh128/cache-apt-pkgs-action@latest
if: matrix.os == 'ubuntu-latest'
with:
packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config xvfb x11-xserver-utils at-spi2-core xdg-desktop-portal-gtk
packages: libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config xvfb x11-xserver-utils at-spi2-core xdg-desktop-portal-gtk
version: 1.0
- name: Install linux dependencies (GTK4) - webkit-gtk6-support branch only

View file

@ -28,7 +28,7 @@ jobs:
- uses: awalsh128/cache-apt-pkgs-action@latest
if: matrix.os == 'ubuntu-24.04'
with:
packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config
packages: libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config
version: 1.0
- name: Setup Go
@ -126,7 +126,7 @@ jobs:
- name: Install linux dependencies ( 24.04 )
if: matrix.os == 'ubuntu-24.04'
run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config
run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config
- name: Generate & Build template '${{ matrix.template }}'
if: matrix.os != 'ubuntu-24.04'

View file

@ -153,7 +153,7 @@ jobs:
- name: Install Linux dev dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev
- name: Install wails3 CLI
run: |

View file

@ -83,7 +83,7 @@ jobs:
- name: Install Linux dependencies
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config
packages: libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config
version: 1.0
- name: Install Wails3 CLI

View file

@ -52,7 +52,7 @@ jobs:
- name: Install linux dependencies (24.04)
if: matrix.os == 'ubuntu-24.04'
run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config
run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config
- name: Setup Go
uses: actions/setup-go@v3

View file

@ -30,6 +30,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
*/
## [Unreleased]
## v3.0.0-alpha.68 - 2026-02-07
## Added
- Add Web API examples in `v3/examples/web-apis/` demonstrating 41 browser APIs including Storage (localStorage, sessionStorage, IndexedDB, Cache API), Network (Fetch, WebSocket, XMLHttpRequest, EventSource, Beacon), Media (Canvas, WebGL, Web Audio, MediaDevices, MediaRecorder, Speech Synthesis), Device (Geolocation, Clipboard, Fullscreen, Device Orientation, Vibration, Gamepad), Performance (Performance API, Mutation Observer, Intersection/Resize Observer), UI (Web Components, Pointer Events, Selection, Dialog, Drag and Drop), and more
- Add WebView API compatibility checker example (`v3/examples/webview-api-check/`) that tests 200+ browser APIs across platforms
- Add `internal/libpath` package for finding native library paths on Linux with parallel search, caching, and support for Flatpak/Snap/Nix
- **WIP:** Add experimental WebKitGTK 6.0 / GTK4 support for Linux, available via `-tags gtk4` (GTK3/WebKit2GTK 4.1 remains the default)
- Note: On tiling window managers (e.g., Hyprland, Sway), Minimize/Maximize operations may not work as expected since the WM controls window geometry
## Changed
- **BREAKING**: Map keys in generated JS/TS bindings are now marked optional to accurately reflect Go map semantics. Map value access in Typescript now returns `T | undefined` instead of `T`, requiring null checks or assertions (#4943) by `@fbbdev`
## Fixed
- Fix file drag-and-drop on Windows not working at non-100% display scaling
- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Windows
- Fix file drop coordinates being in wrong pixel space on Windows (physical vs CSS pixels)
- Fix file drag-and-drop on Linux not working reliably with hover effects
- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Linux
- Fix DPI scaling on Linux/GTK4 by implementing proper PhysicalBounds calculation and fractional scaling support via `gdk_monitor_get_scale` (GTK 4.14+)
- Fix menu items duplicating when creating new windows on Linux/GTK4
- Fix generation of mapped types with enum keys in JS/TS bindings (#4437) by @fbbdev
## v3.0.0-alpha.67 - 2026-02-04
## Added

View file

@ -17,15 +17,10 @@ After processing, the content will be moved to the main changelog and this file
## Added
<!-- New features, capabilities, or enhancements -->
- Add Web API examples in `v3/examples/web-apis/` demonstrating 41 browser APIs including Storage (localStorage, sessionStorage, IndexedDB, Cache API), Network (Fetch, WebSocket, XMLHttpRequest, EventSource, Beacon), Media (Canvas, WebGL, Web Audio, MediaDevices, MediaRecorder, Speech Synthesis), Device (Geolocation, Clipboard, Fullscreen, Device Orientation, Vibration, Gamepad), Performance (Performance API, Mutation Observer, Intersection/Resize Observer), UI (Web Components, Pointer Events, Selection, Dialog, Drag and Drop), and more
- Add WebView API compatibility checker example (`v3/examples/webview-api-check/`) that tests 200+ browser APIs across platforms
- Add `internal/libpath` package for finding native library paths on Linux with parallel search, caching, and support for Flatpak/Snap/Nix
- **WIP:** Add experimental WebKitGTK 6.0 / GTK4 support for Linux, available via `-tags gtk4` (GTK3/WebKit2GTK 4.1 remains the default)
- Note: On tiling window managers (e.g., Hyprland, Sway), Minimize/Maximize operations may not work as expected since the WM controls window geometry
- Add `-tags` flag to `wails3 build` command for passing custom Go build tags (e.g., `wails3 build -tags gtk4`) (#4957)
## Changed
<!-- Changes in existing functionality -->
- **BREAKING**: Map keys in generated JS/TS bindings are now marked optional to accurately reflect Go map semantics. Map value access in Typescript now returns `T | undefined` instead of `T`, requiring null checks or assertions (#4943) by `@fbbdev`
## Fixed
<!-- Bug fixes -->
@ -34,6 +29,9 @@ After processing, the content will be moved to the main changelog and this file
- Fix file drop coordinates being in wrong pixel space on Windows (physical vs CSS pixels)
- Fix file drag-and-drop on Linux not working reliably with hover effects
- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Linux
- Fix window show/hide on Linux/GTK4 sometimes restoring to minimized state by using `gtk_window_present()` (#4957)
- Fix window position get/set on Linux/GTK4 always returning 0,0 by adding X11-conditional support via `XTranslateCoordinates`/`XMoveWindow` (#4957)
- Fix max window size not being enforced on Linux/GTK4 by adding signal-based size clamping to replace removed `gtk_window_set_geometry_hints` (#4957)
- Fix DPI scaling on Linux/GTK4 by implementing proper PhysicalBounds calculation and fractional scaling support via `gdk_monitor_get_scale` (GTK 4.14+)
- Fix menu items duplicating when creating new windows on Linux/GTK4
- Fix generation of mapped types with enum keys in JS/TS bindings (#4437) by @fbbdev

View file

@ -21,6 +21,7 @@ tasks:
ARCH: '{{.ARCH}}'
DEV: '{{.DEV}}'
OUTPUT: '{{.OUTPUT}}'
EXTRA_TAGS: '{{.EXTRA_TAGS}}'
vars:
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
@ -40,7 +41,7 @@ tasks:
cmds:
- go build {{.BUILD_FLAGS}} -o {{.OUTPUT}}
vars:
BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{end}}'
BUILD_FLAGS: '{{if eq .DEV "true"}}{{if .EXTRA_TAGS}}-tags {{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s"{{end}}'
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
env:
@ -65,7 +66,7 @@ tasks:
Docker image '{{.CROSS_IMAGE}}' not found.
Build it first: wails3 task setup:docker
cmds:
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{.CROSS_IMAGE}} darwin {{.DOCKER_ARCH}}
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{if .EXTRA_TAGS}}-e EXTRA_TAGS="{{.EXTRA_TAGS}}"{{end}} {{.CROSS_IMAGE}} darwin {{.DOCKER_ARCH}}
- docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin
- mkdir -p {{.BIN_DIR}}
- mv "bin/{{.APP_NAME}}-darwin-{{.DOCKER_ARCH}}" "{{.OUTPUT}}"

View file

@ -188,7 +188,12 @@ if [ "$GOOS" = "windows" ]; then
LDFLAGS="-s -w -H windowsgui"
fi
go build -ldflags="$LDFLAGS" -o bin/${APP}-${GOOS}-${GOARCH}${EXT} .
TAGS="production"
if [ -n "$EXTRA_TAGS" ]; then
TAGS="${TAGS},${EXTRA_TAGS}"
fi
go build -tags "$TAGS" -trimpath -buildvcs=false -ldflags="$LDFLAGS" -o bin/${APP}-${GOOS}-${GOARCH}${EXT} .
echo "Built: bin/${APP}-${GOOS}-${GOARCH}${EXT}"
SCRIPT
RUN chmod +x /usr/local/bin/build.sh

View file

@ -26,6 +26,7 @@ tasks:
ARCH: '{{.ARCH}}'
DEV: '{{.DEV}}'
OUTPUT: '{{.OUTPUT}}'
EXTRA_TAGS: '{{.EXTRA_TAGS}}'
vars:
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
@ -51,7 +52,7 @@ tasks:
cmds:
- go build {{.BUILD_FLAGS}} -o {{.OUTPUT}}
vars:
BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{end}}'
BUILD_FLAGS: '{{if eq .DEV "true"}}{{if .EXTRA_TAGS}}-tags {{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s"{{end}}'
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
env:
@ -74,7 +75,7 @@ tasks:
Docker image '{{.CROSS_IMAGE}}' not found.
Build it first: wails3 task setup:docker
cmds:
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" "{{.CROSS_IMAGE}}" linux {{.DOCKER_ARCH}}
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{if .EXTRA_TAGS}}-e EXTRA_TAGS="{{.EXTRA_TAGS}}"{{end}} "{{.CROSS_IMAGE}}" linux {{.DOCKER_ARCH}}
- docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin
- mkdir -p {{.BIN_DIR}}
- mv "bin/{{.APP_NAME}}-linux-{{.DOCKER_ARCH}}" "{{.OUTPUT}}"

View file

@ -23,6 +23,7 @@ tasks:
vars:
ARCH: '{{.ARCH}}'
DEV: '{{.DEV}}'
EXTRA_TAGS: '{{.EXTRA_TAGS}}'
vars:
# Default to CGO_ENABLED=0 if not explicitly set
CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}'
@ -47,7 +48,7 @@ tasks:
- cmd: rm -f *.syso
platforms: [linux, darwin]
vars:
BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{end}}'
BUILD_FLAGS: '{{if eq .DEV "true"}}{{if .EXTRA_TAGS}}-tags {{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{end}}'
env:
GOOS: windows
CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}'
@ -68,7 +69,7 @@ tasks:
Build it first: wails3 task setup:docker
cmds:
- task: generate:syso
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{.CROSS_IMAGE}} windows {{.DOCKER_ARCH}}
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{if .EXTRA_TAGS}}-e EXTRA_TAGS="{{.EXTRA_TAGS}}"{{end}} {{.CROSS_IMAGE}} windows {{.DOCKER_ARCH}}
- docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin
- rm -f *.syso
vars:

View file

@ -18,7 +18,10 @@ var validPlatforms = map[string]bool{
"linux": true,
}
func Build(_ *flags.Build, otherArgs []string) error {
func Build(buildFlags *flags.Build, otherArgs []string) error {
if buildFlags.Tags != "" {
otherArgs = append(otherArgs, "EXTRA_TAGS="+buildFlags.Tags)
}
return wrapTask("build", otherArgs)
}

View file

@ -199,6 +199,150 @@ func TestBuildCommand(t *testing.T) {
assert.Equal(t, []string{"CONFIG=release", "ARCH=" + currentArch}, capturedOtherArgs)
}
func TestBuildCommandWithTags(t *testing.T) {
currentOS := runtime.GOOS
currentArch := runtime.GOARCH
// Save original RunTask
originalRunTask := runTaskFunc
defer func() { runTaskFunc = originalRunTask }()
// Mock RunTask to capture the arguments
var capturedOptions *RunTaskOptions
var capturedOtherArgs []string
runTaskFunc = func(options *RunTaskOptions, otherArgs []string) error {
capturedOptions = options
capturedOtherArgs = otherArgs
return nil
}
// Save original os.Args and environment
originalArgs := os.Args
defer func() { os.Args = originalArgs }()
originalGOOS := os.Getenv("GOOS")
originalGOARCH := os.Getenv("GOARCH")
defer func() {
if originalGOOS == "" {
os.Unsetenv("GOOS")
} else {
os.Setenv("GOOS", originalGOOS)
}
if originalGOARCH == "" {
os.Unsetenv("GOARCH")
} else {
os.Setenv("GOARCH", originalGOARCH)
}
}()
os.Unsetenv("GOOS")
os.Unsetenv("GOARCH")
// Test Build command with tags
buildFlags := &flags.Build{}
buildFlags.Tags = "gtk4"
otherArgs := []string{"CONFIG=release"}
err := Build(buildFlags, otherArgs)
assert.NoError(t, err)
assert.Equal(t, currentOS+":build", capturedOptions.Name)
assert.Equal(t, []string{"CONFIG=release", "EXTRA_TAGS=gtk4", "ARCH=" + currentArch}, capturedOtherArgs)
}
func TestBuildCommandWithMultipleTags(t *testing.T) {
currentOS := runtime.GOOS
currentArch := runtime.GOARCH
// Save original RunTask
originalRunTask := runTaskFunc
defer func() { runTaskFunc = originalRunTask }()
// Mock RunTask to capture the arguments
var capturedOptions *RunTaskOptions
var capturedOtherArgs []string
runTaskFunc = func(options *RunTaskOptions, otherArgs []string) error {
capturedOptions = options
capturedOtherArgs = otherArgs
return nil
}
// Save original os.Args and environment
originalArgs := os.Args
defer func() { os.Args = originalArgs }()
originalGOOS := os.Getenv("GOOS")
originalGOARCH := os.Getenv("GOARCH")
defer func() {
if originalGOOS == "" {
os.Unsetenv("GOOS")
} else {
os.Setenv("GOOS", originalGOOS)
}
if originalGOARCH == "" {
os.Unsetenv("GOARCH")
} else {
os.Setenv("GOARCH", originalGOARCH)
}
}()
os.Unsetenv("GOOS")
os.Unsetenv("GOARCH")
// Test Build command with multiple comma-separated tags
buildFlags := &flags.Build{}
buildFlags.Tags = "gtk4,server"
err := Build(buildFlags, nil)
assert.NoError(t, err)
assert.Equal(t, currentOS+":build", capturedOptions.Name)
assert.Equal(t, []string{"EXTRA_TAGS=gtk4,server", "ARCH=" + currentArch}, capturedOtherArgs)
}
func TestBuildCommandWithoutTags(t *testing.T) {
currentOS := runtime.GOOS
currentArch := runtime.GOARCH
// Save original RunTask
originalRunTask := runTaskFunc
defer func() { runTaskFunc = originalRunTask }()
// Mock RunTask to capture the arguments
var capturedOptions *RunTaskOptions
var capturedOtherArgs []string
runTaskFunc = func(options *RunTaskOptions, otherArgs []string) error {
capturedOptions = options
capturedOtherArgs = otherArgs
return nil
}
// Save original os.Args and environment
originalArgs := os.Args
defer func() { os.Args = originalArgs }()
originalGOOS := os.Getenv("GOOS")
originalGOARCH := os.Getenv("GOARCH")
defer func() {
if originalGOOS == "" {
os.Unsetenv("GOOS")
} else {
os.Setenv("GOOS", originalGOOS)
}
if originalGOARCH == "" {
os.Unsetenv("GOARCH")
} else {
os.Setenv("GOARCH", originalGOARCH)
}
}()
os.Unsetenv("GOOS")
os.Unsetenv("GOARCH")
// Test Build command without tags - no EXTRA_TAGS should be present
buildFlags := &flags.Build{}
err := Build(buildFlags, nil)
assert.NoError(t, err)
assert.Equal(t, currentOS+":build", capturedOptions.Name)
assert.Equal(t, []string{"ARCH=" + currentArch}, capturedOtherArgs)
}
func TestPackageCommand(t *testing.T) {
currentOS := runtime.GOOS
currentArch := runtime.GOARCH

View file

@ -2,6 +2,7 @@ package flags
type Build struct {
Common
Tags string `name:"tags" description:"Additional build tags to pass to the Go compiler (comma-separated)"`
}
type Dev struct {

View file

@ -1 +1 @@
v3.0.0-alpha.67
v3.0.0-alpha.68

View file

@ -2,6 +2,31 @@
#include "linux_cgo_gtk4.h"
#ifdef GDK_WINDOWING_X11
#include <gdk/x11/gdkx.h>
#include <dlfcn.h>
// Function pointer types for Xlib functions loaded at runtime via dlsym.
// This avoids a direct link dependency on libX11 - the symbols are resolved
// from GTK4's already-loaded X11 backend.
typedef int (*WailsXMoveWindowFunc)(Display*, Window, int, int);
typedef int (*WailsXFlushFunc)(Display*);
typedef Bool (*WailsXTranslateCoordinatesFunc)(Display*, Window, Window, int, int, int*, int*, Window*);
static WailsXMoveWindowFunc wails_XMoveWindow = NULL;
static WailsXFlushFunc wails_XFlush = NULL;
static WailsXTranslateCoordinatesFunc wails_XTranslateCoordinates = NULL;
static gboolean x11_funcs_resolved = FALSE;
static void resolve_x11_funcs(void) {
if (x11_funcs_resolved) return;
x11_funcs_resolved = TRUE;
wails_XMoveWindow = (WailsXMoveWindowFunc)dlsym(RTLD_DEFAULT, "XMoveWindow");
wails_XFlush = (WailsXFlushFunc)dlsym(RTLD_DEFAULT, "XFlush");
wails_XTranslateCoordinates = (WailsXTranslateCoordinatesFunc)dlsym(RTLD_DEFAULT, "XTranslateCoordinates");
}
#endif
#ifdef WAILS_GTK_DEBUG
#define DEBUG_LOG(fmt, ...) fprintf(stderr, "[GTK4] " fmt "\n", ##__VA_ARGS__)
#else
@ -867,6 +892,102 @@ void clipboard_free_text(char *text) {
}
}
// ============================================================================
// Window position (X11 only)
// ============================================================================
void window_move_x11(GtkWindow *window, int x, int y) {
#ifdef GDK_WINDOWING_X11
GtkNative *native = gtk_widget_get_native(GTK_WIDGET(window));
if (native == NULL) return;
GdkSurface *surface = gtk_native_get_surface(native);
if (surface == NULL) return;
GdkDisplay *display = gdk_surface_get_display(surface);
if (!GDK_IS_X11_DISPLAY(display)) return;
resolve_x11_funcs();
if (wails_XMoveWindow == NULL) return;
Display *xdisplay = gdk_x11_display_get_xdisplay(display);
Window xwindow = gdk_x11_surface_get_xid(GDK_X11_SURFACE(surface));
wails_XMoveWindow(xdisplay, xwindow, x, y);
if (wails_XFlush != NULL) wails_XFlush(xdisplay);
#endif
}
void window_get_position_x11(GtkWindow *window, int *x, int *y) {
*x = 0;
*y = 0;
#ifdef GDK_WINDOWING_X11
GtkNative *native = gtk_widget_get_native(GTK_WIDGET(window));
if (native == NULL) return;
GdkSurface *surface = gtk_native_get_surface(native);
if (surface == NULL) return;
GdkDisplay *display = gdk_surface_get_display(surface);
if (!GDK_IS_X11_DISPLAY(display)) return;
resolve_x11_funcs();
if (wails_XTranslateCoordinates == NULL) return;
Display *xdisplay = gdk_x11_display_get_xdisplay(display);
Window xwindow = gdk_x11_surface_get_xid(GDK_X11_SURFACE(surface));
Window child;
Window root = DefaultRootWindow(xdisplay);
int abs_x, abs_y;
if (wails_XTranslateCoordinates(xdisplay, xwindow, root, 0, 0, &abs_x, &abs_y, &child)) {
*x = abs_x;
*y = abs_y;
}
#endif
}
// ============================================================================
// Window size constraints (max size enforcement for GTK4)
// ============================================================================
static void on_window_size_changed(GObject *object, GParamSpec *pspec, gpointer data) {
GtkWindow *window = GTK_WINDOW(object);
// Don't clamp during fullscreen or maximize - these should bypass max size
// constraints, matching V2 behaviour where geometry hints are suspended.
if (gtk_window_is_fullscreen(window) || gtk_window_is_maximized(window)) return;
int maxW = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(window), "wails-max-width"));
int maxH = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(window), "wails-max-height"));
if (maxW <= 0 && maxH <= 0) return;
int w = gtk_widget_get_width(GTK_WIDGET(window));
int h = gtk_widget_get_height(GTK_WIDGET(window));
gboolean needs_clamp = FALSE;
if (maxW > 0 && w > maxW) { w = maxW; needs_clamp = TRUE; }
if (maxH > 0 && h > maxH) { h = maxH; needs_clamp = TRUE; }
if (needs_clamp) {
gtk_window_set_default_size(window, w, h);
}
}
void window_set_max_size(GtkWindow *window, int maxWidth, int maxHeight) {
g_object_set_data(G_OBJECT(window), "wails-max-width", GINT_TO_POINTER(maxWidth));
g_object_set_data(G_OBJECT(window), "wails-max-height", GINT_TO_POINTER(maxHeight));
// Check if we already connected the signal
gpointer connected = g_object_get_data(G_OBJECT(window), "wails-max-size-connected");
if (connected == NULL) {
g_signal_connect(window, "notify::default-width", G_CALLBACK(on_window_size_changed), NULL);
g_signal_connect(window, "notify::default-height", G_CALLBACK(on_window_size_changed), NULL);
g_object_set_data(G_OBJECT(window), "wails-max-size-connected", GINT_TO_POINTER(1));
}
}
// ============================================================================
// Misc
// ============================================================================

View file

@ -1063,9 +1063,14 @@ func (w *linuxWebviewWindow) size() (int, int) {
}
func (w *linuxWebviewWindow) relativePosition() (int, int) {
// GTK4/Wayland: Window positioning is not reliable
// This is a documented limitation
return 0, 0
x, y := w.position()
monitor := w.getCurrentMonitor()
if monitor == nil {
return x, y
}
var geometry C.GdkRectangle
C.gdk_monitor_get_geometry(monitor, &geometry)
return x - int(geometry.x), y - int(geometry.y)
}
func (w *linuxWebviewWindow) gtkWidget() *C.GtkWidget {
@ -1228,6 +1233,9 @@ func windowSetGeometryHints(window pointer, minWidth, minHeight, maxWidth, maxHe
if minWidth > 0 && minHeight > 0 {
C.gtk_widget_set_size_request(w, C.int(minWidth), C.int(minHeight))
}
if maxWidth > 0 || maxHeight > 0 {
C.window_set_max_size((*C.GtkWindow)(window), C.int(maxWidth), C.int(maxHeight))
}
}
func (w *linuxWebviewWindow) setResizable(resizable bool) {
@ -1235,11 +1243,17 @@ func (w *linuxWebviewWindow) setResizable(resizable bool) {
}
func (w *linuxWebviewWindow) move(x, y int) {
// C-side GDK_IS_X11_DISPLAY check handles X11 vs Wayland correctly,
// including XWayland and GDK_BACKEND=x11 scenarios.
C.window_move_x11(w.gtkWindow(), C.int(x), C.int(y))
}
func (w *linuxWebviewWindow) position() (int, int) {
// GTK4/Wayland: Cannot reliably get window position
return 0, 0
// C-side GDK_IS_X11_DISPLAY check handles X11 vs Wayland correctly,
// returning 0,0 on non-X11 displays.
var x, y C.int
C.window_get_position_x11(w.gtkWindow(), &x, &y)
return int(x), int(y)
}
func (w *linuxWebviewWindow) unfullscreen() {
@ -1255,7 +1269,7 @@ func (w *linuxWebviewWindow) windowShow() {
if w.gtkWidget() == nil {
return
}
C.gtk_widget_set_visible(w.gtkWidget(), gtkBool(true))
C.gtk_window_present(w.gtkWindow())
}
func (w *linuxWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) {

View file

@ -155,6 +155,13 @@ void setupWindowEventControllers(GtkWindow *window, GtkWidget *webview, uintptr_
void beginWindowDrag(GtkWindow *window, int button, double x, double y, guint32 timestamp);
void beginWindowResize(GtkWindow *window, GdkSurfaceEdge edge, int button, double x, double y, guint32 timestamp);
// ============================================================================
// Window position (X11 only)
// ============================================================================
void window_move_x11(GtkWindow *window, int x, int y);
void window_get_position_x11(GtkWindow *window, int *x, int *y);
// ============================================================================
// Drag and drop (GtkDropTarget for GTK4)
// ============================================================================
@ -189,6 +196,12 @@ void show_message_dialog(GtkWindow *parent, const char *heading, const char *bod
char* clipboard_get_text_sync(void);
void clipboard_free_text(char *text);
// ============================================================================
// Window size constraints
// ============================================================================
void window_set_max_size(GtkWindow *window, int maxWidth, int maxHeight);
// ============================================================================
// Misc
// ============================================================================