- One i3bar block per session (individual colors: red/green/gray) - Workspace prefix: 10-vmux[W], 3-auth[? 3m] - Wait type markers: ⚡ permission, ? question, ! generic - Wait duration: [⚡ 30s], [? 3m], [? 1h15m] - Standalone mode only (separate bar, no i3status wrapping) - Removed isTerminal and i3status wrapping code
130 lines
3.0 KiB
Go
130 lines
3.0 KiB
Go
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)
|
|
}
|
|
}
|