Skip to content

Commit 957f810

Browse files
committed
Linux: simple opacity function implemented
1 parent f9c59d9 commit 957f810

2 files changed

Lines changed: 60 additions & 5 deletions

File tree

internal/ui/manager.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,9 @@ func (u *UIManager) GetPosition() (int, int) {
149149

150150
// SetOpacity applies background-only transparency to the widget window.
151151
// opacityPercent should be 25, 50, 75, or 100.
152-
// At < 100% the background color becomes transparent via Win32 color-key;
153-
// all text, icons and other content remain fully opaque.
152+
// On Windows the background color becomes transparent via Win32 color-key;
153+
// on Linux the whole window opacity is adjusted via _NET_WM_WINDOW_OPACITY
154+
// with mapped values to keep content readable.
154155
func (u *UIManager) SetOpacity(opacityPercent int) {
155156
setWindowOpacity(opacityPercent)
156157
u.widget.Canvas().Refresh(u.widget.Content())

internal/ui/x11_linux.go

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,60 @@ func getWindowPosition() (int, int) {
108108
return x, y
109109
}
110110

111-
// setWindowOpacity is a no-op on Linux (xprop/_NET_WM_WINDOW_OPACITY requires
112-
// a compositing manager; omitted for simplicity).
113-
func setWindowOpacity(_ int) {}
111+
// setWindowOpacity applies whole-window transparency on Linux/X11 by setting
112+
// the _NET_WM_WINDOW_OPACITY property via xprop. This requires a compositing
113+
// manager (Picom, Mutter, KWin, etc.) to take effect.
114+
//
115+
// Unlike Windows (which supports background-only color-key transparency),
116+
// X11 _NET_WM_WINDOW_OPACITY affects the entire window including content.
117+
// To keep text and icons readable, the user-facing opacity values are mapped
118+
// to less aggressive X11 values:
119+
//
120+
// User 100% → X11 100% (fully opaque)
121+
// User 75% → X11 85%
122+
// User 50% → X11 70%
123+
// User 25% → X11 55%
124+
func setWindowOpacity(opacityPercent int) {
125+
// Map user-facing opacity to X11 whole-window opacity so content stays
126+
// readable. The window background dims while text/icons remain legible.
127+
x11Percent := opacityPercent
128+
switch {
129+
case opacityPercent >= 100:
130+
x11Percent = 100
131+
case opacityPercent >= 75:
132+
x11Percent = 85
133+
case opacityPercent >= 50:
134+
x11Percent = 70
135+
case opacityPercent >= 25:
136+
x11Percent = 55
137+
default:
138+
x11Percent = 55
139+
}
140+
141+
go func() {
142+
// Give the window a moment to be mapped if called at startup.
143+
time.Sleep(600 * time.Millisecond)
144+
145+
if x11Percent >= 100 {
146+
// Remove the property entirely — fully opaque.
147+
cmd := exec.Command("xprop", "-name", widgetTitle, "-remove", "_NET_WM_WINDOW_OPACITY")
148+
if err := cmd.Run(); err != nil {
149+
log.Printf("Linux: setWindowOpacity: failed to remove _NET_WM_WINDOW_OPACITY: %v", err)
150+
}
151+
return
152+
}
153+
154+
// _NET_WM_WINDOW_OPACITY is a 32-bit cardinal where 0xFFFFFFFF = fully opaque.
155+
opacity := uint64(x11Percent) * 0xFFFFFFFF / 100
156+
val := strconv.FormatUint(opacity, 10)
157+
158+
cmd := exec.Command("xprop", "-name", widgetTitle,
159+
"-f", "_NET_WM_WINDOW_OPACITY", "32c",
160+
"-set", "_NET_WM_WINDOW_OPACITY", val)
161+
if err := cmd.Run(); err != nil {
162+
log.Printf("Linux: setWindowOpacity: xprop failed (is x11-utils installed? is a compositor running?): %v", err)
163+
return
164+
}
165+
log.Printf("Linux: set window opacity to %d%% (user: %d%%, _NET_WM_WINDOW_OPACITY=%s)", x11Percent, opacityPercent, val)
166+
}()
167+
}

0 commit comments

Comments
 (0)