From 5da6f9fb0a25e55c302268f0d524189bffd53e21 Mon Sep 17 00:00:00 2001 From: Terrence Ezrol Date: Sun, 3 Aug 2025 11:28:37 -0400 Subject: [PATCH] Some wayland issues At least with experimental cinnamon w/ xwayland there is a minor bug: DISPLAY may be set to :1, despite xwayland listening on :0 in this case connections to :1 will indicate not ready/retry in this case to mimic most X11 libraries try :0 if we get this on :1, or :1 if we are on :0 otherwise we will retry the current DISPLAY requested to be sure this is a valid approch we also check `xset q` this must return as working with the original display to try the other one --- main.go | 23 +++++++++++++++++++++++ x11probe/probe.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/main.go b/main.go index 59020fa..94f1003 100644 --- a/main.go +++ b/main.go @@ -52,7 +52,30 @@ func main() { log.Fatalf("Connection probe error: %v", err) } + if status == x11probe.X11TemporarilyUnavailable { + //retry 1x, but change display if :0/:1 assuming wayland dead socket + if display == ":1" && x11probe.CheckDisplay(display) { + log.Printf("is wayland scoket wrong? try :0") + display = ":0" + connType, target = resolveDisplay(display) + fmt.Printf("Proxying to %s (%s)\n", target, connTypeString(connType)) + } else { + if display == ":0" && x11probe.CheckDisplay(display) { + log.Printf("is wayland scoket wrong? try :1") + display = ":1" + connType, target = resolveDisplay(display) + fmt.Printf("Proxying to %s (%s)\n", target, connTypeString(connType)) + } + } + status, err = x11probe.ProbeX11Socket(connType, target, timeout) + if err != nil { + log.Fatalf("Connection probe error: %v", err) + } + } + switch status { + case x11probe.X11TemporarilyUnavailable: + log.Fatalf("Target connection is temporarily unavalible, is X11 running?") case x11probe.X11Live: log.Println("X11 socket is live and accepting connections") case x11probe.X11AuthRequired: diff --git a/x11probe/probe.go b/x11probe/probe.go index 4d593bf..a9de38d 100644 --- a/x11probe/probe.go +++ b/x11probe/probe.go @@ -1,11 +1,15 @@ package x11probe import ( + "errors" "fmt" "io" "log" "net" + "os" + "os/exec" "strings" + "syscall" "time" ) @@ -23,6 +27,7 @@ const ( X11Dead X11Status = iota X11Live X11AuthRequired + X11TemporarilyUnavailable ) func ProbeX11Socket(connType ConnType, target string, timeout time.Duration) (X11Status, error) { @@ -35,12 +40,18 @@ func ProbeX11Socket(connType ConnType, target string, timeout time.Duration) (X1 case Unix: conn, err = net.DialTimeout("unix", target, timeout) if err != nil { + if isResourceTemporarilyUnavailable(err) { // Check for specific error + return X11TemporarilyUnavailable, nil + } return X11Dead, fmt.Errorf("unix dial failed: %w", err) } case TCP: addr := parseTCPAddress(target) conn, err = net.DialTimeout("tcp", addr, timeout) if err != nil { + if isResourceTemporarilyUnavailable(err) { // Check for specific error + return X11TemporarilyUnavailable, nil + } return X11Dead, fmt.Errorf("TCP dial failed: %w", err) } } @@ -60,6 +71,18 @@ func ProbeX11Socket(connType ConnType, target string, timeout time.Duration) (X1 return status, nil } +func isResourceTemporarilyUnavailable(err error) bool { + // Match exact error string first + if strings.Contains(err.Error(), "resource temporarily unavailable") { + return true + } + // Fallback to syscall-level check + if errors.Is(err, syscall.EAGAIN) { + return true + } + return false +} + func parseTCPAddress(target string) string { parts := strings.SplitN(target, ":", 2) @@ -123,3 +146,15 @@ func readResponse(conn net.Conn, timeout time.Duration) (X11Status, error) { } } + +func CheckDisplay(display string) bool { + cmd := exec.Command("xset", "q") + cmd.Env = append(os.Environ(), fmt.Sprintf("DISPLAY=%s", display)) + + if err := cmd.Run(); err != nil { + fmt.Fprintf(os.Stderr, "xset q failed for DISPLAY=%s: %v\n", display, err) + return false + } + + return true +}