Skip to content

Commit 4f4d706

Browse files
committed
Ubuntu 26 improved app Look & Feel
1 parent f44ec31 commit 4f4d706

8 files changed

Lines changed: 612 additions & 103 deletions

File tree

Makefile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# Weather Widget Build Configuration
22
BINARY_NAME=weatherwidget
33
CMD_PATH=./cmd/weatherwidget/
4-
GO_CMD=/usr/local/go/bin/go
5-
4+
GO_CMD=/usr/bin/go
65
# Detect OS
76
ifeq ($(OS),Windows_NT)
87
BINARY_NAME=weatherwidget.exe

internal/ui/drag_linux.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@ var (
1919

2020
// enableWindowDrag enables drag-to-reposition detection on Linux.
2121
//
22-
// Since the title bar is removed, most window managers still allow moving
23-
// via Alt+left-click drag or Super+drag. This function starts a background
24-
// poller that detects when the window position changes and calls onDragEnd
25-
// so the caller can persist the new coordinates.
22+
// On Wayland, direct window-move interception is not possible through the
23+
// protocol. Instead we poll the window position (via wmctrl or xdotool
24+
// through XWayland) and detect when the user has moved the window using
25+
// the compositor's built-in drag mechanism (typically Super+drag or
26+
// Alt+drag on GNOME).
27+
//
28+
// On X11, the same polling approach is used with xdotool.
2629
//
27-
// Requires xdotool to be installed.
30+
// The onDragEnd callback is invoked when a position change is detected
31+
// so the caller can persist the new coordinates.
2832
func enableWindowDrag(onDragEnd func()) {
2933
linuxDragMu.Lock()
3034
defer linuxDragMu.Unlock()
@@ -39,7 +43,12 @@ func enableWindowDrag(onDragEnd func()) {
3943
linuxLastX, linuxLastY = getWindowPosition()
4044

4145
go pollWindowPosition(linuxDragStop)
42-
log.Println("Linux drag: position poller started")
46+
47+
if isWayland() {
48+
log.Println("Linux/Wayland: drag position poller started (use Super+drag or Alt+drag to reposition)")
49+
} else {
50+
log.Println("Linux/X11: drag position poller started")
51+
}
4352
}
4453

4554
// notifyLinuxMoveByUs should be called before programmatic moves so the
@@ -86,7 +95,7 @@ func pollWindowPosition(stop chan struct{}) {
8695
linuxDragMu.Unlock()
8796

8897
if cb != nil {
89-
log.Printf("Linux drag: position changed from (%d,%d) to (%d,%d), saving", lastX, lastY, x, y)
98+
log.Printf("Linux: position changed from (%d,%d) to (%d,%d), saving", lastX, lastY, x, y)
9099
cb()
91100
}
92101
}

internal/ui/manager.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,11 @@ type UIManager struct {
2424
}
2525

2626
// NewUIManager creates a new UIManager and its main widget window.
27-
// After the window is shown, call applyToolWindowStyle to set Win32
28-
// WS_EX_TOOLWINDOW and HWND_TOPMOST styles (no-op on non-Windows).
27+
// On Linux the window is created without decorations (borderless) via
28+
// CreateSplashWindow. On Windows decorations are removed post-creation
29+
// by applyToolWindowStyle using Win32 API calls.
2930
func NewUIManager(app fyne.App, lm *i18n.LocaleManager) *UIManager {
30-
w := app.NewWindow(widgetTitle)
31-
w.SetFixedSize(true)
32-
w.SetPadded(false)
31+
w := createWidgetWindow(app, widgetTitle)
3332

3433
return &UIManager{
3534
app: app,

internal/ui/theme.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ui
22

33
import (
44
"image/color"
5+
"runtime"
56
"sync/atomic"
67

78
"fyne.io/fyne/v2"
@@ -16,6 +17,14 @@ var transparencyKey = color.NRGBA{R: 1, G: 1, B: 1, A: 255}
1617
// transparencyActive is 1 when a color-key background should be used.
1718
var transparencyActive atomic.Int32
1819

20+
// linuxOpacityAlpha stores the alpha value for Linux background transparency.
21+
// Range: 0 (fully transparent) to 255 (fully opaque). Default is 255.
22+
var linuxOpacityAlpha atomic.Int32
23+
24+
func init() {
25+
linuxOpacityAlpha.Store(255)
26+
}
27+
1928
// SetTransparencyActive switches the background color key on or off.
2029
func SetTransparencyActive(active bool) {
2130
if active {
@@ -25,6 +34,19 @@ func SetTransparencyActive(active bool) {
2534
}
2635
}
2736

37+
// SetLinuxOpacityAlpha sets the background alpha value for Linux transparency.
38+
// opacityPercent should be 0–100. This is converted to a 0–255 alpha value.
39+
func SetLinuxOpacityAlpha(opacityPercent int) {
40+
alpha := opacityPercent * 255 / 100
41+
if alpha < 0 {
42+
alpha = 0
43+
}
44+
if alpha > 255 {
45+
alpha = 255
46+
}
47+
linuxOpacityAlpha.Store(int32(alpha))
48+
}
49+
2850
// widgetTheme is a Fyne theme that replaces the window background with the
2951
// color key when transparency is active, leaving all other colors unchanged.
3052
type widgetTheme struct {
@@ -40,6 +62,15 @@ func (t *widgetTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant)
4062
if transparencyActive.Load() == 1 {
4163
switch name {
4264
case theme.ColorNameBackground, theme.ColorNameOverlayBackground:
65+
if runtime.GOOS == "linux" {
66+
// On Linux/Wayland, use actual alpha transparency.
67+
// The Fyne OpenGL/EGL renderer respects the alpha channel,
68+
// giving us background-only transparency (text/icons stay opaque).
69+
alpha := uint8(linuxOpacityAlpha.Load())
70+
return color.NRGBA{R: 30, G: 30, B: 30, A: alpha}
71+
}
72+
// On Windows, use the color-key approach (Win32 LWA_COLORKEY
73+
// makes this exact color invisible).
4374
return transparencyKey
4475
}
4576
}

internal/ui/window_linux.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//go:build linux
2+
3+
package ui
4+
5+
import (
6+
"log"
7+
8+
"fyne.io/fyne/v2"
9+
"fyne.io/fyne/v2/driver/desktop"
10+
)
11+
12+
// createWidgetWindow creates the main widget window on Linux.
13+
//
14+
// On Linux (especially Wayland/GNOME), window decorations cannot be removed
15+
// after creation. We use Fyne's CreateSplashWindow() which sets the GLFW
16+
// Decorated hint to false before the window is created, producing a truly
17+
// borderless window on both Wayland and X11.
18+
//
19+
// On Windows this is not needed because Win32 API calls strip decorations
20+
// post-creation.
21+
func createWidgetWindow(app fyne.App, title string) fyne.Window {
22+
drv, ok := app.Driver().(desktop.Driver)
23+
if !ok {
24+
log.Println("Linux: desktop driver not available, falling back to standard window")
25+
w := app.NewWindow(title)
26+
return w
27+
}
28+
29+
w := drv.CreateSplashWindow()
30+
w.SetTitle(title)
31+
w.SetFixedSize(true)
32+
w.SetPadded(false)
33+
34+
log.Println("Linux: created undecorated widget window via CreateSplashWindow")
35+
return w
36+
}

internal/ui/window_other.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//go:build !windows && !linux
2+
3+
package ui
4+
5+
import (
6+
"fyne.io/fyne/v2"
7+
)
8+
9+
// createWidgetWindow creates the main widget window on unsupported platforms.
10+
func createWidgetWindow(app fyne.App, title string) fyne.Window {
11+
w := app.NewWindow(title)
12+
w.SetFixedSize(true)
13+
w.SetPadded(false)
14+
return w
15+
}

internal/ui/window_windows.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//go:build windows
2+
3+
package ui
4+
5+
import (
6+
"fyne.io/fyne/v2"
7+
)
8+
9+
// createWidgetWindow creates the main widget window on Windows.
10+
// Decorations are removed later via Win32 API calls in applyToolWindowStyle.
11+
func createWidgetWindow(app fyne.App, title string) fyne.Window {
12+
w := app.NewWindow(title)
13+
w.SetFixedSize(true)
14+
w.SetPadded(false)
15+
return w
16+
}

0 commit comments

Comments
 (0)