diff --git a/pkg/display/display.go b/pkg/display/display.go index 0b4174e..cc3f900 100644 --- a/pkg/display/display.go +++ b/pkg/display/display.go @@ -281,6 +281,147 @@ func (s *Service) handleWSMessage(msg WSMessage) (any, bool, error) { case "browser:open-file": path, _ := msg.Data["path"].(string) result, handled, err = s.Core().PERFORM(browser.TaskOpenFile{Path: path}) + case "window:list": + result, handled, err = s.Core().QUERY(window.QueryWindowList{}) + case "window:get": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + result, handled, err = s.Core().QUERY(window.QueryWindowByName{Name: name}) + case "window:focused": + result, handled, err = s.GetFocusedWindow(), true, nil + case "window:create": + var opts CreateWindowOptions + encoded, _ := json.Marshal(msg.Data) + if err := json.Unmarshal(encoded, &opts); err != nil { + return nil, false, fmt.Errorf("ws: invalid window create options: %w", err) + } + info, createErr := s.CreateWindow(opts) + if createErr != nil { + return nil, false, createErr + } + result, handled, err = info, true, nil + case "window:close": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + result, handled, err = nil, true, s.CloseWindow(name) + case "window:position": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + x, _ := msg.Data["x"].(float64) + y, _ := msg.Data["y"].(float64) + result, handled, err = nil, true, s.SetWindowPosition(name, int(x), int(y)) + case "window:size": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + width, _ := msg.Data["width"].(float64) + height, _ := msg.Data["height"].(float64) + result, handled, err = nil, true, s.SetWindowSize(name, int(width), int(height)) + case "window:bounds": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + x, _ := msg.Data["x"].(float64) + y, _ := msg.Data["y"].(float64) + width, _ := msg.Data["width"].(float64) + height, _ := msg.Data["height"].(float64) + result, handled, err = nil, true, s.SetWindowBounds(name, int(x), int(y), int(width), int(height)) + case "window:maximize": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + result, handled, err = nil, true, s.MaximizeWindow(name) + case "window:minimize": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + result, handled, err = nil, true, s.MinimizeWindow(name) + case "window:restore": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + result, handled, err = nil, true, s.RestoreWindow(name) + case "window:focus": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + result, handled, err = nil, true, s.FocusWindow(name) + case "focus:set": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + result, handled, err = nil, true, s.FocusSet(name) + case "window:visibility": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + visible, _ := msg.Data["visible"].(bool) + result, handled, err = nil, true, s.SetWindowVisibility(name, visible) + case "window:title-set": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + title, e := wsRequire(msg.Data, "title") + if e != nil { + return nil, false, e + } + result, handled, err = nil, true, s.SetWindowTitle(name, title) + case "window:title-get": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + title, titleErr := s.GetWindowTitle(name) + if titleErr != nil { + return nil, false, titleErr + } + result, handled, err = title, true, nil + case "window:always-on-top": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + alwaysOnTop, _ := msg.Data["alwaysOnTop"].(bool) + result, handled, err = nil, true, s.SetWindowAlwaysOnTop(name, alwaysOnTop) + case "window:background-colour": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + red, _ := msg.Data["red"].(float64) + green, _ := msg.Data["green"].(float64) + blue, _ := msg.Data["blue"].(float64) + alpha, _ := msg.Data["alpha"].(float64) + result, handled, err = nil, true, s.SetWindowBackgroundColour(name, uint8(red), uint8(green), uint8(blue), uint8(alpha)) + case "window:opacity": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + opacity, _ := msg.Data["opacity"].(float64) + result, handled, err = nil, true, s.SetWindowOpacity(name, float32(opacity)) + case "window:fullscreen": + name, e := wsRequire(msg.Data, "name") + if e != nil { + return nil, false, e + } + fullscreen, _ := msg.Data["fullscreen"].(bool) + result, handled, err = nil, true, s.SetWindowFullscreen(name, fullscreen) case "dock:show": result, handled, err = s.Core().PERFORM(dock.TaskShowIcon{}) case "dock:hide": diff --git a/pkg/display/display_test.go b/pkg/display/display_test.go index c426add..cf9cf95 100644 --- a/pkg/display/display_test.go +++ b/pkg/display/display_test.go @@ -950,6 +950,106 @@ func TestHandleWSMessage_Extended_Good(t *testing.T) { assert.Equal(t, "side-by-side", suggestion.Mode) }) + t.Run("window list", func(t *testing.T) { + result, handled, err := svc.handleWSMessage(WSMessage{Action: "window:list"}) + require.NoError(t, err) + assert.True(t, handled) + windows, ok := result.([]window.WindowInfo) + require.True(t, ok) + assert.GreaterOrEqual(t, len(windows), 2) + }) + + t.Run("window get", func(t *testing.T) { + result, handled, err := svc.handleWSMessage(WSMessage{ + Action: "window:get", + Data: map[string]any{"name": "editor"}, + }) + require.NoError(t, err) + assert.True(t, handled) + info, ok := result.(*window.WindowInfo) + require.True(t, ok) + require.NotNil(t, info) + assert.Equal(t, "editor", info.Name) + }) + + t.Run("window focused", func(t *testing.T) { + require.NoError(t, svc.FocusWindow("assistant")) + result, handled, err := svc.handleWSMessage(WSMessage{Action: "window:focused"}) + require.NoError(t, err) + assert.True(t, handled) + assert.Equal(t, "assistant", result) + }) + + t.Run("window title get", func(t *testing.T) { + result, handled, err := svc.handleWSMessage(WSMessage{ + Action: "window:title-get", + Data: map[string]any{"name": "editor"}, + }) + require.NoError(t, err) + assert.True(t, handled) + assert.Equal(t, "Editor", result) + }) + + t.Run("window position and bounds", func(t *testing.T) { + _, handled, err := svc.handleWSMessage(WSMessage{ + Action: "window:position", + Data: map[string]any{ + "name": "assistant", + "x": float64(40), + "y": float64(50), + }, + }) + require.NoError(t, err) + assert.True(t, handled) + + _, handled, err = svc.handleWSMessage(WSMessage{ + Action: "window:bounds", + Data: map[string]any{ + "name": "assistant", + "x": float64(60), + "y": float64(70), + "width": float64(800), + "height": float64(640), + }, + }) + require.NoError(t, err) + assert.True(t, handled) + + info, err := svc.GetWindowInfo("assistant") + require.NoError(t, err) + require.NotNil(t, info) + assert.Equal(t, 60, info.X) + assert.Equal(t, 70, info.Y) + assert.Equal(t, 800, info.Width) + assert.Equal(t, 640, info.Height) + }) + + t.Run("window create and close", func(t *testing.T) { + result, handled, err := svc.handleWSMessage(WSMessage{ + Action: "window:create", + Data: map[string]any{ + "name": "ws-new", + "title": "WS New", + "url": "/ws-new", + "width": float64(500), + "height": float64(350), + }, + }) + require.NoError(t, err) + assert.True(t, handled) + created, ok := result.(*window.WindowInfo) + require.True(t, ok) + require.NotNil(t, created) + assert.Equal(t, "ws-new", created.Name) + + _, handled, err = svc.handleWSMessage(WSMessage{ + Action: "window:close", + Data: map[string]any{"name": "ws-new"}, + }) + require.NoError(t, err) + assert.True(t, handled) + }) + t.Run("layout stack", func(t *testing.T) { _, handled, err := svc.handleWSMessage(WSMessage{ Action: "layout:stack",