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 "" }