refactor(ax): expose live window visibility state
Some checks failed
Security Scan / security (push) Failing after 30s
Test / test (push) Successful in 1m22s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-31 08:47:38 +00:00
parent 53a4114224
commit 762806d316
11 changed files with 121 additions and 26 deletions

View file

@ -46,23 +46,23 @@ Returns:
```go
type WindowInfo struct {
Name string
Title string
X int
Y int
Width int
Height int
IsVisible bool
IsFocused bool
IsMaximized bool
IsMinimized bool
Name string
Title string
X int
Y int
Width int
Height int
Visible bool
Minimized bool
Maximized bool
Focused bool
}
```
### ListWindowInfos
```go
func (s *Service) ListWindowInfos() []*WindowInfo
func (s *Service) ListWindowInfos() []WindowInfo
```
### Window Position & Size

View file

@ -177,6 +177,8 @@ func TestOpenWindow_Defaults_Good(t *testing.T) {
assert.Equal(t, "Core", info.Title)
assert.Greater(t, info.Width, 0)
assert.Greater(t, info.Height, 0)
assert.True(t, info.Visible)
assert.False(t, info.Minimized)
}
func TestGetWindowInfo_Good(t *testing.T) {
@ -279,6 +281,18 @@ func TestMaximizeWindow_Good(t *testing.T) {
assert.True(t, info.Maximized)
}
func TestMinimizeWindow_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_ = svc.OpenWindow(window.Window{Name: "min-win"})
err := svc.MinimizeWindow("min-win")
assert.NoError(t, err)
info, _ := svc.GetWindowInfo("min-win")
assert.True(t, info.Minimized)
}
func TestRestoreWindow_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
@ -325,8 +339,16 @@ func TestSetWindowVisibility_Good(t *testing.T) {
err := svc.SetWindowVisibility("vis-win", false)
assert.NoError(t, err)
info, err := svc.GetWindowInfo("vis-win")
require.NoError(t, err)
assert.False(t, info.Visible)
err = svc.SetWindowVisibility("vis-win", true)
assert.NoError(t, err)
info, err = svc.GetWindowInfo("vis-win")
require.NoError(t, err)
assert.True(t, info.Visible)
}
func TestSetWindowAlwaysOnTop_Good(t *testing.T) {

View file

@ -15,8 +15,8 @@ The `Service` struct is the main entry point for the display logic.
- **Window Management:**
- `OpenWindow(spec window.Window) error`: Opens the default window using manager defaults.
- `CreateWindow(spec window.Window) (*window.WindowInfo, error)`: Opens a named window and returns its info.
- `GetWindowInfo(name string) (*window.WindowInfo, error)`: Queries a single window.
- `ListWindowInfos() []window.WindowInfo`: Queries all tracked windows.
- `GetWindowInfo(name string) (*window.WindowInfo, error)`: Queries a single window, including visibility, minimization, focus, and maximized state.
- `ListWindowInfos() []window.WindowInfo`: Queries all tracked windows with the same live state fields.
- `SetWindowBounds(name string, x, y, width, height int) error` - preferred when position and size change together.
- `SetWindowPosition(name string, x, y int) error`
- `SetWindowSize(name string, width, height int) error`

View file

@ -7,6 +7,8 @@ type WindowInfo struct {
Y int `json:"y"`
Width int `json:"width"`
Height int `json:"height"`
Visible bool `json:"visible"`
Minimized bool `json:"minimized"`
Maximized bool `json:"maximized"`
Focused bool `json:"focused"`
}

View file

@ -15,6 +15,7 @@ func (m *MockPlatform) CreateWindow(options PlatformWindowOptions) PlatformWindo
name: options.Name, title: options.Title, url: options.URL,
width: options.Width, height: options.Height,
x: options.X, y: options.Y,
visible: !options.Hidden,
}
m.Windows = append(m.Windows, w)
return w
@ -31,7 +32,8 @@ func (m *MockPlatform) GetWindows() []PlatformWindow {
type MockWindow struct {
name, title, url string
width, height, x, y int
maximised, focused bool
maximised, minimised bool
focused bool
visible, alwaysOnTop bool
backgroundColour [4]uint8
closed bool
@ -44,6 +46,8 @@ func (w *MockWindow) Title() string { return w.title }
func (w *MockWindow) Position() (int, int) { return w.x, w.y }
func (w *MockWindow) Size() (int, int) { return w.width, w.height }
func (w *MockWindow) IsMaximised() bool { return w.maximised }
func (w *MockWindow) IsMinimised() bool { return w.minimised }
func (w *MockWindow) IsVisible() bool { return w.visible }
func (w *MockWindow) IsFocused() bool { return w.focused }
func (w *MockWindow) SetTitle(title string) { w.title = title }
func (w *MockWindow) SetBounds(x, y, width, height int) {
@ -54,9 +58,9 @@ func (w *MockWindow) SetSize(width, height int) { w.width = width; w.
func (w *MockWindow) SetBackgroundColour(r, g, b, a uint8) { w.backgroundColour = [4]uint8{r, g, b, a} }
func (w *MockWindow) SetVisibility(visible bool) { w.visible = visible }
func (w *MockWindow) SetAlwaysOnTop(alwaysOnTop bool) { w.alwaysOnTop = alwaysOnTop }
func (w *MockWindow) Maximise() { w.maximised = true }
func (w *MockWindow) Restore() { w.maximised = false }
func (w *MockWindow) Minimise() {}
func (w *MockWindow) Maximise() { w.maximised = true; w.minimised = false }
func (w *MockWindow) Restore() { w.maximised = false; w.minimised = false }
func (w *MockWindow) Minimise() { w.maximised = false; w.minimised = true }
func (w *MockWindow) Focus() { w.focused = true }
func (w *MockWindow) Close() {
w.closed = true

View file

@ -13,6 +13,7 @@ func (m *mockPlatform) CreateWindow(options PlatformWindowOptions) PlatformWindo
name: options.Name, title: options.Title, url: options.URL,
width: options.Width, height: options.Height,
x: options.X, y: options.Y,
visible: !options.Hidden,
}
m.windows = append(m.windows, w)
return w
@ -29,11 +30,11 @@ func (m *mockPlatform) GetWindows() []PlatformWindow {
type mockWindow struct {
name, title, url string
width, height, x, y int
maximised, focused bool
maximised, minimised bool
focused bool
visible, alwaysOnTop bool
backgroundColour [4]uint8
closed bool
minimised bool
fullscreened bool
eventHandlers []func(WindowEvent)
fileDropHandlers []func(paths []string, targetID string)
@ -44,6 +45,8 @@ func (w *mockWindow) Title() string { return w.title }
func (w *mockWindow) Position() (int, int) { return w.x, w.y }
func (w *mockWindow) Size() (int, int) { return w.width, w.height }
func (w *mockWindow) IsMaximised() bool { return w.maximised }
func (w *mockWindow) IsMinimised() bool { return w.minimised }
func (w *mockWindow) IsVisible() bool { return w.visible }
func (w *mockWindow) IsFocused() bool { return w.focused }
func (w *mockWindow) SetTitle(title string) { w.title = title }
func (w *mockWindow) SetBounds(x, y, width, height int) {
@ -54,9 +57,9 @@ func (w *mockWindow) SetSize(width, height int) { w.width = width; w.
func (w *mockWindow) SetBackgroundColour(r, g, b, a uint8) { w.backgroundColour = [4]uint8{r, g, b, a} }
func (w *mockWindow) SetVisibility(visible bool) { w.visible = visible }
func (w *mockWindow) SetAlwaysOnTop(alwaysOnTop bool) { w.alwaysOnTop = alwaysOnTop }
func (w *mockWindow) Maximise() { w.maximised = true }
func (w *mockWindow) Restore() { w.maximised = false }
func (w *mockWindow) Minimise() { w.minimised = true }
func (w *mockWindow) Maximise() { w.maximised = true; w.minimised = false }
func (w *mockWindow) Restore() { w.maximised = false; w.minimised = false }
func (w *mockWindow) Minimise() { w.maximised = false; w.minimised = true }
func (w *mockWindow) Focus() { w.focused = true }
func (w *mockWindow) Close() {
w.closed = true

View file

@ -34,6 +34,8 @@ type PlatformWindow interface {
Position() (int, int)
Size() (int, int)
IsMaximised() bool
IsMinimised() bool
IsVisible() bool
IsFocused() bool
// Mutations

View file

@ -90,7 +90,14 @@ func (s *Service) queryWindowList() []WindowInfo {
x, y := platformWindow.Position()
width, height := platformWindow.Size()
result = append(result, WindowInfo{
Name: name, Title: platformWindow.Title(), X: x, Y: y, Width: width, Height: height,
Name: name,
Title: platformWindow.Title(),
X: x,
Y: y,
Width: width,
Height: height,
Visible: platformWindow.IsVisible(),
Minimized: platformWindow.IsMinimised(),
Maximized: platformWindow.IsMaximised(),
Focused: platformWindow.IsFocused(),
})
@ -107,7 +114,14 @@ func (s *Service) queryWindowByName(name string) *WindowInfo {
x, y := platformWindow.Position()
width, height := platformWindow.Size()
return &WindowInfo{
Name: name, Title: platformWindow.Title(), X: x, Y: y, Width: width, Height: height,
Name: name,
Title: platformWindow.Title(),
X: x,
Y: y,
Width: width,
Height: height,
Visible: platformWindow.IsVisible(),
Minimized: platformWindow.IsMinimised(),
Maximized: platformWindow.IsMaximised(),
Focused: platformWindow.IsFocused(),
}
@ -219,7 +233,18 @@ func (s *Service) taskOpenWindow(t TaskOpenWindow) (any, bool, error) {
}
x, y := platformWindow.Position()
width, height := platformWindow.Size()
info := WindowInfo{Name: platformWindow.Name(), Title: platformWindow.Title(), X: x, Y: y, Width: width, Height: height}
info := WindowInfo{
Name: platformWindow.Name(),
Title: platformWindow.Title(),
X: x,
Y: y,
Width: width,
Height: height,
Visible: platformWindow.IsVisible(),
Minimized: platformWindow.IsMinimised(),
Maximized: platformWindow.IsMaximised(),
Focused: platformWindow.IsFocused(),
}
// Attach platform event listeners that convert to IPC actions
s.trackWindow(platformWindow)

View file

@ -39,6 +39,8 @@ func TestTaskOpenWindow_Good(t *testing.T) {
assert.True(t, handled)
info := result.(WindowInfo)
assert.Equal(t, "test", info.Name)
assert.True(t, info.Visible)
assert.False(t, info.Minimized)
}
func TestTaskOpenWindow_Declarative_Good(t *testing.T) {
@ -322,6 +324,11 @@ func TestTaskMinimize_Good(t *testing.T) {
require.True(t, ok)
mw := pw.(*mockWindow)
assert.True(t, mw.minimised)
result, _, err := c.QUERY(QueryWindowByName{Name: "test"})
require.NoError(t, err)
info := result.(*WindowInfo)
assert.True(t, info.Minimized)
}
func TestTaskMinimize_Bad(t *testing.T) {
@ -470,11 +477,21 @@ func TestTaskSetVisibility_Good(t *testing.T) {
mw := pw.(*mockWindow)
assert.True(t, mw.visible)
result, _, err := c.QUERY(QueryWindowByName{Name: "test"})
require.NoError(t, err)
info := result.(*WindowInfo)
assert.True(t, info.Visible)
// Now hide it
_, handled, err = c.PERFORM(TaskSetVisibility{Name: "test", Visible: false})
require.NoError(t, err)
assert.True(t, handled)
assert.False(t, mw.visible)
result, _, err = c.QUERY(QueryWindowByName{Name: "test"})
require.NoError(t, err)
info = result.(*WindowInfo)
assert.False(t, info.Visible)
}
func TestTaskSetVisibility_Bad(t *testing.T) {

View file

@ -63,6 +63,8 @@ func (windowHandle *wailsWindow) Title() string { return windowHandle.tit
func (windowHandle *wailsWindow) Position() (int, int) { return windowHandle.w.Position() }
func (windowHandle *wailsWindow) Size() (int, int) { return windowHandle.w.Size() }
func (windowHandle *wailsWindow) IsMaximised() bool { return windowHandle.w.IsMaximised() }
func (windowHandle *wailsWindow) IsMinimised() bool { return windowHandle.w.IsMinimised() }
func (windowHandle *wailsWindow) IsVisible() bool { return windowHandle.w.IsVisible() }
func (windowHandle *wailsWindow) IsFocused() bool { return windowHandle.w.IsFocused() }
func (windowHandle *wailsWindow) SetTitle(title string) {
windowHandle.title = title

View file

@ -189,6 +189,7 @@ type WebviewWindow struct {
x, y int
width, height int
maximised bool
minimised bool
focused bool
visible bool
alwaysOnTop bool
@ -232,6 +233,16 @@ func (w *WebviewWindow) IsMaximised() bool {
defer w.mu.RUnlock()
return w.maximised
}
func (w *WebviewWindow) IsMinimised() bool {
w.mu.RLock()
defer w.mu.RUnlock()
return w.minimised
}
func (w *WebviewWindow) IsVisible() bool {
w.mu.RLock()
defer w.mu.RUnlock()
return w.visible
}
func (w *WebviewWindow) IsFocused() bool {
w.mu.RLock()
defer w.mu.RUnlock()
@ -269,17 +280,24 @@ func (w *WebviewWindow) SetAlwaysOnTop(alwaysOnTop bool) {
func (w *WebviewWindow) Maximise() {
w.mu.Lock()
w.maximised = true
w.minimised = false
w.mu.Unlock()
}
func (w *WebviewWindow) Restore() {
w.mu.Lock()
w.maximised = false
w.minimised = false
w.fullscreen = false
w.mu.Unlock()
}
func (w *WebviewWindow) Minimise() {}
func (w *WebviewWindow) Minimise() {
w.mu.Lock()
w.maximised = false
w.minimised = true
w.mu.Unlock()
}
func (w *WebviewWindow) Focus() {
w.mu.Lock()