199 lines
5.1 KiB
Go
199 lines
5.1 KiB
Go
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 doSync() {
|
|
// 1. Read the desired state from the config file.
|
|
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 startxplacement.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
|
|
}
|