v1.8.69: smart X display auto-detection and xauth fix
Automatically finds active display from X11 sockets and processes, locates .Xauthority from display owner in /home/*, and merges xauth cookies so root can use another user's X session. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
83
main.py
83
main.py
@@ -11,31 +11,90 @@ import subprocess
|
|||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
def _find_active_xauthority():
|
||||||
|
"""Find .Xauthority from the user who owns the active X display."""
|
||||||
|
# 1. Already set and works
|
||||||
|
if os.environ.get("XAUTHORITY") and os.path.isfile(os.environ["XAUTHORITY"]):
|
||||||
|
return os.environ["XAUTHORITY"]
|
||||||
|
# 2. Search /home/*/.Xauthority for all users
|
||||||
|
try:
|
||||||
|
for entry in os.scandir("/home"):
|
||||||
|
if entry.is_dir():
|
||||||
|
xauth = os.path.join(entry.path, ".Xauthority")
|
||||||
|
if os.path.isfile(xauth):
|
||||||
|
return xauth
|
||||||
|
except PermissionError:
|
||||||
|
pass
|
||||||
|
# 3. Check /root/.Xauthority
|
||||||
|
root_xauth = "/root/.Xauthority"
|
||||||
|
if os.path.isfile(root_xauth):
|
||||||
|
return root_xauth
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _find_active_display():
|
||||||
|
"""Detect active X display from running sessions."""
|
||||||
|
display = os.environ.get("DISPLAY")
|
||||||
|
if display:
|
||||||
|
return display
|
||||||
|
# Parse from /tmp/.X11-unix sockets
|
||||||
|
x11_dir = "/tmp/.X11-unix"
|
||||||
|
if os.path.isdir(x11_dir):
|
||||||
|
try:
|
||||||
|
sockets = sorted(os.listdir(x11_dir))
|
||||||
|
for s in sockets:
|
||||||
|
if s.startswith("X"):
|
||||||
|
return ":" + s[1:]
|
||||||
|
except PermissionError:
|
||||||
|
pass
|
||||||
|
# Parse from active X/xrdp/vnc processes
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["ps", "-eo", "args"], capture_output=True, text=True, timeout=3
|
||||||
|
)
|
||||||
|
for line in result.stdout.splitlines():
|
||||||
|
# Xorg :50 ... or Xvnc :1 ...
|
||||||
|
for prefix in ("Xorg ", "Xvnc ", "Xtigervnc ", "xrdp-sesman"):
|
||||||
|
if prefix in line:
|
||||||
|
for part in line.split():
|
||||||
|
if part.startswith(":") and part[1:].split(".")[0].isdigit():
|
||||||
|
return part
|
||||||
|
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||||
|
pass
|
||||||
|
return ":0"
|
||||||
|
|
||||||
|
|
||||||
def _ensure_display_access():
|
def _ensure_display_access():
|
||||||
"""Auto-fix X display authorization for root and non-owner users."""
|
"""Auto-fix X display authorization for root and non-owner users."""
|
||||||
display = os.environ.get("DISPLAY")
|
# Step 1: Find active display
|
||||||
if not display:
|
display = _find_active_display()
|
||||||
# Try common displays
|
os.environ["DISPLAY"] = display
|
||||||
for d in (":0", ":1", ":0.0"):
|
|
||||||
os.environ["DISPLAY"] = d
|
# Step 2: Find and set XAUTHORITY from display owner
|
||||||
display = d
|
xauth = _find_active_xauthority()
|
||||||
break
|
if xauth:
|
||||||
|
os.environ["XAUTHORITY"] = xauth
|
||||||
|
|
||||||
|
# Step 3: Try xhost to allow local connections
|
||||||
try:
|
try:
|
||||||
subprocess.run(["xhost", "+local:"], stdout=subprocess.DEVNULL,
|
subprocess.run(["xhost", "+local:"], stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL, timeout=3)
|
stderr=subprocess.DEVNULL, timeout=3)
|
||||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||||
pass
|
pass
|
||||||
# Fallback: generate xauth cookie if xhost not available
|
|
||||||
if display:
|
# Step 4: Verify display works, try xauth merge if not
|
||||||
try:
|
try:
|
||||||
subprocess.run(["xdpyinfo"], stdout=subprocess.DEVNULL,
|
subprocess.run(["xdpyinfo"], stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL, timeout=3, check=True)
|
stderr=subprocess.DEVNULL, timeout=3, check=True)
|
||||||
|
return # Works!
|
||||||
except (FileNotFoundError, subprocess.TimeoutExpired, subprocess.CalledProcessError):
|
except (FileNotFoundError, subprocess.TimeoutExpired, subprocess.CalledProcessError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Step 5: Last resort — copy xauth cookies from display owner
|
||||||
|
if xauth and xauth != os.path.expanduser("~/.Xauthority"):
|
||||||
try:
|
try:
|
||||||
xauth_file = os.path.expanduser("~/.Xauthority")
|
|
||||||
os.environ.setdefault("XAUTHORITY", xauth_file)
|
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
["xauth", "generate", display, ".", "trusted"],
|
["xauth", "merge", xauth],
|
||||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=5
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=5
|
||||||
)
|
)
|
||||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||||
|
|||||||
BIN
releases/ServerManager-v1.8.69-linux-x64
Normal file
BIN
releases/ServerManager-v1.8.69-linux-x64
Normal file
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
"""Version info for ServerManager."""
|
"""Version info for ServerManager."""
|
||||||
|
|
||||||
__version__ = "1.8.68"
|
__version__ = "1.8.69"
|
||||||
__app_name__ = "ServerManager"
|
__app_name__ = "ServerManager"
|
||||||
__author__ = "aibot777"
|
__author__ = "aibot777"
|
||||||
__description__ = "Desktop GUI for managing remote servers"
|
__description__ = "Desktop GUI for managing remote servers"
|
||||||
|
|||||||
Reference in New Issue
Block a user