diff options
| author | Castor Regex <[email protected]> | 2025-08-24 22:11:17 -0500 |
|---|---|---|
| committer | Jeff Carr <[email protected]> | 2025-08-24 22:11:17 -0500 |
| commit | 9d5bd8d5b92fc91a6ff03398cd9f338dce4ca6b4 (patch) | |
| tree | da140efe0e333dc75dbe4a613a82fb4ce628e65d /sync_terminals.go | |
| parent | e37836bb61cfb06711b9d2ce35e89f680af448c6 (diff) | |
feat: add terminal synchronization program
Diffstat (limited to 'sync_terminals.go')
| -rw-r--r-- | sync_terminals.go | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/sync_terminals.go b/sync_terminals.go new file mode 100644 index 0000000..2c557ba --- /dev/null +++ b/sync_terminals.go @@ -0,0 +1,228 @@ +package main + +import ( + "bufio" + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "time" +) + +// DesiredState represents a terminal window configuration from the file. +type DesiredState struct { + Path string + Geometry string // WIDTHxHEIGHT+X+Y + Workspace string +} + +// CurrentState represents an open window's properties from wmctrl. +type CurrentState struct { + WindowID string + Workspace string + Geometry string // X,Y,Width,Height + Path string +} + +func main() { + // 1. Read the desired state from the config file. + configFile := "/home/jcarr/go/src/gemini/xstartplacement.out" + desiredStates, err := parseDesiredState(configFile) + if err != nil { + fmt.Printf("Error parsing config file: %v\n", err) + return + } + + // 2. Get the current state of all terminal windows. + currentStates, err := getCurrentState() + if err != nil { + fmt.Printf("Error getting current window state: %v\n", err) + return + } + + // 3. Create a map of current windows for easy lookup. + currentMap := make(map[string]bool) + for _, window := range currentStates { + // Normalize the path for comparison. + currentMap[window.Path] = true + } + + // 4. Compare desired state with current state and launch missing terminals. + for _, desired := range desiredStates { + if _, exists := currentMap[desired.Path]; !exists { + fmt.Printf("Terminal for path '%s' not found. Launching...\n", desired.Path) + launchTerminal(desired) + } else { + fmt.Printf("Terminal for path '%s' already exists. Skipping.\n", desired.Path) + } + } + + fmt.Println("Terminal synchronization complete.") +} + +// launchTerminal launches and configures a new mate-terminal. +func launchTerminal(state DesiredState) { + originalDir, _ := os.Getwd() + if err := os.Chdir(state.Path); err != nil { + fmt.Printf("Failed to change directory to %s: %v\n", state.Path, err) + return + } + defer os.Chdir(originalDir) + + windowsBefore, err := getWindowList() + if err != nil { + fmt.Printf("Failed to get initial window list: %v\n", err) + return + } + + cmd := exec.Command("mate-terminal", "--geometry", state.Geometry) + if err := cmd.Start(); err != nil { + fmt.Printf("Failed to launch terminal for %s: %v\n", state.Path, err) + return + } + + var newWindowID string + for i := 0; i < 10; i++ { + time.Sleep(500 * time.Millisecond) + windowsAfter, _ := getWindowList() + newWindowID = findNewWindow(windowsBefore, windowsAfter) + if newWindowID != "" { + break + } + } + + if newWindowID == "" { + fmt.Printf("Could not find new window for %s\n", state.Path) + return + } + + exec.Command("wmctrl", "-i", "-r", newWindowID, "-t", state.Workspace).Run() + finalTitle := fmt.Sprintf("jcarr@framebook: %s", state.Path) + exec.Command("wmctrl", "-i", "-r", newWindowID, "-T", finalTitle).Run() + + fmt.Printf("Successfully launched terminal for %s\n", state.Path) +} + +// parseDesiredState reads the xstartplacement.out file. +func parseDesiredState(filePath string) ([]DesiredState, error) { + file, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer file.Close() + + var states []DesiredState + scanner := bufio.NewScanner(file) + var currentState DesiredState + homeDir, _ := os.UserHomeDir() + + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, " Title: ") { + title := strings.TrimSpace(strings.TrimPrefix(line, " Title: ")) + parts := strings.SplitN(title, ": ", 2) + if len(parts) == 2 { + path := parts[1] + if strings.HasPrefix(path, "~") { + path = filepath.Join(homeDir, path[1:]) + } + currentState.Path = path + } + } else if strings.HasPrefix(line, " Geometry: ") { + geomStr := strings.TrimSpace(strings.TrimPrefix(line, " Geometry: ")) + var x, y, w, h string + fmt.Sscanf(geomStr, "X=%s Y=%s Width=%s Height=%s", &x, &y, &w, &h) + x = strings.TrimSuffix(x, ",") + y = strings.TrimSuffix(y, ",") + w = strings.TrimSuffix(w, ",") + currentState.Geometry = fmt.Sprintf("%sx%s+%s+%s", w, h, x, y) + } else if strings.HasPrefix(line, " Workspace: ") { + currentState.Workspace = strings.TrimSpace(strings.TrimPrefix(line, " Workspace: ")) + } else if line == "---" { + if currentState.Path != "" { + states = append(states, currentState) + } + currentState = DesiredState{} + } + } + if currentState.Path != "" { + states = append(states, currentState) + } + return states, scanner.Err() +} + +// getCurrentState gets all open mate-terminal windows. +func getCurrentState() ([]CurrentState, error) { + cmd := exec.Command("wmctrl", "-lG") + var out bytes.Buffer + cmd.Stdout = &out + if err := cmd.Run(); err != nil { + return nil, err + } + + var states []CurrentState + scanner := bufio.NewScanner(&out) + homeDir, _ := os.UserHomeDir() + + for scanner.Scan() { + line := scanner.Text() + if !strings.Contains(line, "jcarr@framebook") { + continue + } + + fields := strings.Fields(line) + if len(fields) < 8 { + continue + } + + title := strings.Join(fields[7:], " ") + parts := strings.SplitN(title, ": ", 2) + if len(parts) != 2 { + continue + } + + path := parts[1] + if strings.HasPrefix(path, "~") { + path = filepath.Join(homeDir, path[1:]) + } + + states = append(states, CurrentState{ + WindowID: fields[0], + Workspace: fields[1], + Geometry: fmt.Sprintf("%s,%s,%s,%s", fields[2], fields[3], fields[4], fields[5]), + Path: path, + }) + } + return states, nil +} + +// Helper functions +func getWindowList() (map[string]string, error) { + cmd := exec.Command("wmctrl", "-l") + var out bytes.Buffer + cmd.Stdout = &out + if err := cmd.Run(); err != nil { + return nil, err + } + windows := make(map[string]string) + scanner := bufio.NewScanner(&out) + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) > 0 { + windows[fields[0]] = strings.Join(fields[3:], " ") + } + } + return windows, nil +} + +func findNewWindow(before, after map[string]string) string { + for id := range after { + if _, ok := before[id]; !ok { + return id + } + } + return "" +}
\ No newline at end of file |
