package main import ( "context" "fmt" "image" "log" "git.milar.in/milarin/hypr" "git.milar.in/milarin/slices" ) func main() { client, err := hypr.GetDefaultClient() if err != nil { panic(err) } events, err := client.Subscribe(context.Background(), hypr.EventTypeFullscreen) if err != nil { panic(err) } if err := RestoreWindowPositions(client); err != nil { panic(err) } for event := range events { if len(event.Data) != 1 || event.Data[0] != "0" { continue } if err := RestoreWindowPositions(client); err != nil { panic(err) } } } func RestoreWindowPositions(client *hypr.Client) error { monitors, err := client.GetMonitors() if err != nil { return err } monitorBoundsByID := slices.ToMap(monitors, func(m *hypr.Monitor) (int, image.Rectangle) { return m.ID, image.Rect(m.X, m.Y, m.X+m.Width, m.Y+m.Height) }) windows, err := client.GetWindows() if err != nil { return err } for _, window := range windows { // ignore all special workspaces because they can't be moved between monitors if window.Workspace.ID < 0 { continue } windowBounds := image.Rect( window.At[0], window.At[1], window.At[0]+window.Size[0], window.At[1]+window.Size[1]) monitorBounds := monitorBoundsByID[window.MonitorID] if windowBounds.In(monitorBounds) { continue } boundsOfActualMonitor := GetMonitorBoundsByWindowBounds(windowBounds, monitorBoundsByID) shouldPos := image.Pt(-1, -1) if boundsOfActualMonitor == nil { // use coords [0,0] respective to the wanted monitor if window is completely OOB shouldPos = monitorBounds.Min } else { // calculate new position based on the position of the window and its actual monitor diff := windowBounds.Min.Sub(boundsOfActualMonitor.Min) shouldPos = monitorBounds.Min.Add(diff) } cmd := fmt.Sprintf( "movewindowpixel exact %d %d,address:%s", shouldPos.X, shouldPos.Y, window.Address) if err := client.DispatchExpectOK(cmd); err != nil { log.Printf("invalid position could NOT be restored successfully for window (class: '%s' | title: '%s')\n", window.Class, window.Title) continue } log.Printf("invalid position restored for window (class: '%s' | title: '%s')\n", window.Class, window.Title) } return nil } // GetMonitorBoundsByWindowBounds returns the monitor bounds which contains windowBounds // or image.Rect(-1, -1, -1, -1) if windowBounds is outside any monitor bounds func GetMonitorBoundsByWindowBounds(windowBounds image.Rectangle, monitorBoundsByID map[int]image.Rectangle) *image.Rectangle { for _, monitorBounds := range monitorBoundsByID { if windowBounds.In(monitorBounds) { newBounds := windowBounds return &newBounds } } return nil }