summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCastor Regex <[email protected]>2025-08-24 22:11:17 -0500
committerJeff Carr <[email protected]>2025-08-24 22:11:17 -0500
commit9d5bd8d5b92fc91a6ff03398cd9f338dce4ca6b4 (patch)
treeda140efe0e333dc75dbe4a613a82fb4ce628e65d
parente37836bb61cfb06711b9d2ce35e89f680af448c6 (diff)
feat: add terminal synchronization program
-rw-r--r--launch_terminal.go (renamed from showAll.go)0
-rw-r--r--stuff.go.disabled121
-rw-r--r--sync_terminals.go228
3 files changed, 349 insertions, 0 deletions
diff --git a/showAll.go b/launch_terminal.go
index be3a5d7..be3a5d7 100644
--- a/showAll.go
+++ b/launch_terminal.go
diff --git a/stuff.go.disabled b/stuff.go.disabled
new file mode 100644
index 0000000..e50ff3d
--- /dev/null
+++ b/stuff.go.disabled
@@ -0,0 +1,121 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "reflect"
+
+ "github.com/BurntSushi/xgb"
+ "github.com/BurntSushi/xgb/xproto"
+)
+
+func main() {
+ conn, err := xgb.NewConn()
+ if err != nil {
+ fmt.Println("Failed to connect to X server:", err)
+ os.Exit(1)
+ }
+ defer conn.Close()
+
+ /*
+ // Start the terminal (replace with your app)
+ go func() {
+ if err := exec.Command("mate-terminal", "--title", "Workspace1-Terminal").Start(); err != nil {
+ fmt.Println("Error starting terminal:", err)
+ }
+ }()
+
+ // Wait for the window to appear
+ time.Sleep(2 * time.Second)
+ */
+
+ // Get the root window
+ setup := xproto.Setup(conn)
+ root := setup.DefaultScreen(conn).Root
+
+ // List children windows
+ reply, err := xproto.QueryTree(conn, root).Reply()
+ if err != nil {
+ fmt.Println("Failed to query windows:", err)
+ os.Exit(1)
+ }
+
+ // Find the window with the specified title
+ var target xproto.Window
+ for _, child := range reply.Children {
+ // fmt.Printf("child: %+v\n", child)
+ /*
+ // Get the atom for _NET_WM_NAME
+ atomReply, err := xproto.InternAtom(conn, true, uint16(len("_NET_WM_NAME")), "_NET_WM_NAME").Reply()
+ if err != nil {
+ log.Fatalf("Failed to intern atom _NET_WM_NAME: %v", err)
+ }
+ netWmNameAtom := atomReply.Atom // Correct field to use
+ */
+
+ /*
+ // Get the property for _NET_WM_NAME
+ nameReply, err := xproto.GetProperty(conn, false, child, netWmNameAtom, xproto.AtomString, 0, (1<<32)-1).Reply()
+ if err != nil {
+ log.Printf("Failed to get property _NET_WM_NAME: %v", err)
+ } else if len(nameReply.Value) > 0 {
+ fmt.Printf("Window name: %s\n", string(nameReply.Value))
+ }
+ */
+
+ /*
+ // Get the atom for _NET_WM_NAME
+ atomReply, err := xproto.InternAtom(conn, true, uint16(len("_NET_WM_NAME")), "_NET_WM_NAME").Reply()
+ if err != nil {
+ log.Fatalf("Failed to intern atom _NET_WM_NAME: %v", err)
+ } else {
+ fmt.Printf("found atomic name: %s\n", string(atomReply.Value))
+ }
+ netWmNameAtom := atomReply.Atom
+ */
+
+ /*
+ // Get the property for _NET_WM_NAME
+ nameReply, err := xproto.GetProperty(conn, false, child, netWmNameAtom, xproto.AtomString, 0, (1<<32)-1).Reply()
+ if err != nil {
+ log.Printf("Failed to get property _NET_WM_NAME: %v", err)
+ } else if len(nameReply.Value) > 0 {
+ fmt.Printf("Window name: %s\n", string(nameReply.Value))
+ }
+ */
+
+ geomReply, err := xproto.GetGeometry(conn, xproto.Drawable(child)).Reply()
+ if err != nil {
+ fmt.Printf("err: %+v\n", err)
+ // fmt.Printf("child geomReply: %+v\n", geomReply)
+ } else {
+ fmt.Printf("child geomReply: %+v\n", geomReply)
+ }
+
+ nameReply, err := xproto.GetProperty(conn, false, child, xproto.AtomWmName, xproto.AtomString, 0, (1<<32)-1).Reply()
+ if err != nil {
+ // fmt.Printf("child err: %+v\n", err)
+ } else {
+ fmt.Printf("child %+v nameReply: %+v %s\n", reflect.TypeOf(child), nameReply, string(nameReply.Value))
+ }
+ if err != nil || len(nameReply.Value) == 0 {
+ continue
+ }
+
+ name := string(nameReply.Value)
+ if name == "Terminal" {
+ target = child
+ break
+ }
+ }
+
+ if target == 0 {
+ fmt.Println("Window not found.")
+ os.Exit(1)
+ }
+
+ // Move the window to workspace 1 and set its geometry
+ xproto.ConfigureWindow(conn, target, xproto.ConfigWindowX|xproto.ConfigWindowY|xproto.ConfigWindowWidth|xproto.ConfigWindowHeight,
+ []uint32{100, 100, 800, 600})
+ fmt.Println("Window moved and resized.")
+}
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