package main import ( "encoding/json" "fmt" "log" "os" "time" ) // I3BarBlock represents a single block in the i3bar protocol. type I3BarBlock struct { FullText string `json:"full_text"` ShortText string `json:"short_text,omitempty"` Color string `json:"color"` Name string `json:"name"` Markup string `json:"markup,omitempty"` } // formatI3BarBlocks builds one i3bar block per session, each with its own color. // For Needs Input sessions: shows wait type icon and duration. func formatI3BarBlocks(sessions []SessionInfo, now time.Time) []I3BarBlock { if len(sessions) == 0 { return []I3BarBlock{{FullText: "vmux: no sessions", Color: "#888888", Name: "vmux"}} } blocks := make([]I3BarBlock, 0, len(sessions)) for _, s := range sessions { name := shortName(s) if s.Workspace != "" { name = s.Workspace + "-" + name } var marker, color string switch s.State { case "Needs Input": marker = waitTypeMarker(s.WaitType) if s.WaitingSince != nil { marker += " " + shortDuration(now.Sub(*s.WaitingSince)) } marker = "[" + marker + "]" color = "#ff0000" case "Working": marker = "[W]" color = "#00ff00" case "Idle": marker = "[I]" color = "#888888" default: marker = "[?]" color = "#888888" } blocks = append(blocks, I3BarBlock{ FullText: name + marker, Color: color, Name: "vmux-" + s.SessionID, }) } return blocks } // waitTypeMarker returns a short icon for the wait type. func waitTypeMarker(waitType string) string { switch waitType { case "permission": return "⚡" case "question": return "?" default: return "!" } } // shortDuration formats a duration as compact string for i3bar. func shortDuration(d time.Duration) string { if d < time.Minute { return fmt.Sprintf("%ds", int(d.Seconds())) } m := int(d.Minutes()) if m < 60 { return fmt.Sprintf("%dm", m) } return fmt.Sprintf("%dh%dm", m/60, m%60) } // queryVmuxBlocks fetches sessions from daemon and returns i3bar blocks. func queryVmuxBlocks(sockPath string) []I3BarBlock { client := NewClient(sockPath) resp, err := client.Send(Request{Action: "list"}) if err != nil { return []I3BarBlock{{FullText: "vmux: offline", Color: "#888888", Name: "vmux"}} } if !resp.OK { return []I3BarBlock{{FullText: "vmux: error", Color: "#888888", Name: "vmux"}} } return formatI3BarBlocks(resp.Sessions, time.Now()) } // runI3Bar runs the i3bar protocol loop on stdout, polling the daemon every 2s. func runI3Bar(sockPath string) { os.Stdout.Write([]byte("{\"version\":1}\n")) os.Stdout.Write([]byte("[\n")) os.Stdout.Sync() first := true for { blocks := queryVmuxBlocks(sockPath) data, err := json.Marshal(blocks) if err != nil { log.Printf("i3bar marshal error: %v", err) time.Sleep(2 * time.Second) continue } var line string if first { line = string(data) + "\n" } else { line = "," + string(data) + "\n" } os.Stdout.Write([]byte(line)) os.Stdout.Sync() first = false time.Sleep(2 * time.Second) } }