diff --git a/pkg/mcp/tools_layout.go b/pkg/mcp/tools_layout.go index 1719ce5..be2f3f9 100644 --- a/pkg/mcp/tools_layout.go +++ b/pkg/mcp/tools_layout.go @@ -182,7 +182,7 @@ func (s *Subsystem) registerLayoutTools(server *mcp.Server) { mcp.AddTool(server, &mcp.Tool{Name: "layout_delete", Description: "Delete a saved layout"}, s.layoutDelete) mcp.AddTool(server, &mcp.Tool{Name: "layout_get", Description: "Get a specific layout by name"}, s.layoutGet) mcp.AddTool(server, &mcp.Tool{Name: "layout_tile", Description: "Tile windows in a grid arrangement"}, s.layoutTile) - mcp.AddTool(server, &mcp.Tool{Name: "layout_snap", Description: "Snap a window to a screen edge or corner"}, s.layoutSnap) + mcp.AddTool(server, &mcp.Tool{Name: "layout_snap", Description: "Snap a window to a screen edge, corner, or center"}, s.layoutSnap) mcp.AddTool(server, &mcp.Tool{Name: "layout_stack", Description: "Stack windows in a cascade pattern"}, s.layoutStack) mcp.AddTool(server, &mcp.Tool{Name: "layout_workflow", Description: "Apply a preset workflow layout"}, s.layoutWorkflow) } diff --git a/pkg/window/service.go b/pkg/window/service.go index be5bce5..a72d18f 100644 --- a/pkg/window/service.go +++ b/pkg/window/service.go @@ -244,13 +244,7 @@ func (s *Service) trackWindow(platformWindow PlatformWindow) { case "resize": if data := event.Data; data != nil { width, _ := data["width"].(int) - if width == 0 { - width, _ = data["w"].(int) - } height, _ := data["height"].(int) - if height == 0 { - height, _ = data["h"].(int) - } _ = s.Core().ACTION(ActionWindowResized{Name: event.Name, Width: width, Height: height}) } case "close": @@ -452,7 +446,7 @@ var snapPosMap = map[string]SnapPosition{ "top": SnapTop, "bottom": SnapBottom, "top-left": SnapTopLeft, "top-right": SnapTopRight, "bottom-left": SnapBottomLeft, "bottom-right": SnapBottomRight, - "center": SnapCenter, "centre": SnapCenter, + "center": SnapCenter, } func (s *Service) taskSnapWindow(name, position string) error { diff --git a/pkg/window/service_screen_test.go b/pkg/window/service_screen_test.go index 98f283d..4dee1f8 100644 --- a/pkg/window/service_screen_test.go +++ b/pkg/window/service_screen_test.go @@ -99,6 +99,48 @@ func TestTaskSnapWindow_UsesPrimaryScreenSize(t *testing.T) { assert.Equal(t, 1000, info.Height) } +func TestTaskSnapWindow_Center_Good(t *testing.T) { + _, c := newTestWindowServiceWithScreen(t, []screen.Screen{ + { + ID: "1", Name: "Primary", IsPrimary: true, + Bounds: screen.Rect{X: 0, Y: 0, Width: 2000, Height: 1000}, + WorkArea: screen.Rect{X: 0, Y: 0, Width: 2000, Height: 1000}, + }, + }) + + _, _, err := c.PERFORM(TaskOpenWindow{Window: Window{Name: "snap", Width: 400, Height: 300}}) + require.NoError(t, err) + + _, handled, err := c.PERFORM(TaskSnapWindow{Name: "snap", Position: "center"}) + require.NoError(t, err) + assert.True(t, handled) + + result, _, err := c.QUERY(QueryWindowByName{Name: "snap"}) + require.NoError(t, err) + info := result.(*WindowInfo) + assert.Equal(t, 800, info.X) + assert.Equal(t, 350, info.Y) + assert.Equal(t, 400, info.Width) + assert.Equal(t, 300, info.Height) +} + +func TestTaskSnapWindow_LegacyCentre_Bad(t *testing.T) { + _, c := newTestWindowServiceWithScreen(t, []screen.Screen{ + { + ID: "1", Name: "Primary", IsPrimary: true, + Bounds: screen.Rect{X: 0, Y: 0, Width: 2000, Height: 1000}, + WorkArea: screen.Rect{X: 0, Y: 0, Width: 2000, Height: 1000}, + }, + }) + + _, _, err := c.PERFORM(TaskOpenWindow{Window: Window{Name: "snap", Width: 400, Height: 300}}) + require.NoError(t, err) + + _, handled, err := c.PERFORM(TaskSnapWindow{Name: "snap", Position: "centre"}) + assert.True(t, handled) + assert.Error(t, err) +} + func TestTaskTileWindows_UsesPrimaryWorkAreaOrigin(t *testing.T) { _, c := newTestWindowServiceWithScreen(t, []screen.Screen{ { diff --git a/pkg/window/service_test.go b/pkg/window/service_test.go index db25a22..b36b530 100644 --- a/pkg/window/service_test.go +++ b/pkg/window/service_test.go @@ -226,6 +226,52 @@ func TestFileDrop_Good(t *testing.T) { mu.Unlock() } +func TestWindowResizeEvent_UsesCanonicalPayload_Good(t *testing.T) { + svc, c := newTestWindowService(t) + + _, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "resize-test"}}) + + var ( + resized ActionWindowResized + seen bool + ) + c.RegisterAction(func(_ *core.Core, msg core.Message) error { + if a, ok := msg.(ActionWindowResized); ok { + resized = a + seen = true + } + return nil + }) + + pw, ok := svc.Manager().Get("resize-test") + require.True(t, ok) + mw := pw.(*mockWindow) + + mw.emit(WindowEvent{ + Type: "resize", + Name: "resize-test", + Data: map[string]any{"width": 111, "height": 222}, + }) + + assert.True(t, seen) + assert.Equal(t, "resize-test", resized.Name) + assert.Equal(t, 111, resized.Width) + assert.Equal(t, 222, resized.Height) + + resized = ActionWindowResized{} + seen = false + mw.emit(WindowEvent{ + Type: "resize", + Name: "resize-test", + Data: map[string]any{"w": 333, "h": 444}, + }) + + assert.True(t, seen) + assert.Equal(t, "resize-test", resized.Name) + assert.Equal(t, 0, resized.Width) + assert.Equal(t, 0, resized.Height) +} + // --- TaskMinimize --- func TestTaskMinimize_Good(t *testing.T) { diff --git a/pkg/window/tiling.go b/pkg/window/tiling.go index 9e3a3e2..28a073b 100644 --- a/pkg/window/tiling.go +++ b/pkg/window/tiling.go @@ -180,7 +180,7 @@ func (m *Manager) TileWindows(mode TileMode, names []string, screenW, screenH in return nil } -// SnapWindow snaps a window to a screen edge/corner/centre. +// SnapWindow snaps a window to a screen edge, corner, or center. func (m *Manager) SnapWindow(name string, pos SnapPosition, screenW, screenH int, origin ...int) error { originX, originY := layoutOrigin(origin) pw, ok := m.Get(name)