Skip to content

Commit

Permalink
Fix remote control at X11 greeter by correctly grabbing display and X…
Browse files Browse the repository at this point in the history
…authority from Xorg process.
  • Loading branch information
bitbound committed Aug 30, 2023
1 parent ff11f18 commit 55f3a80
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 79 deletions.
208 changes: 130 additions & 78 deletions Agent/Services/Linux/AppLauncherLinux.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
using Microsoft.AspNetCore.SignalR.Client;
using Immense.RemoteControl.Shared;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
using Remotely.Agent.Interfaces;
using Remotely.Shared.Extensions;
using Remotely.Shared.Models;
using Remotely.Shared.Services;
using Remotely.Shared.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web.Services.Description;

namespace Remotely.Agent.Services.Linux;


public class AppLauncherLinux : IAppLauncher
{
private readonly string _rcBinaryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Desktop", EnvironmentHelper.DesktopExecutableFileName);
private readonly IProcessInvoker _processInvoker;
private readonly ConnectionInfo _connectionInfo;
private readonly ILogger<AppLauncherLinux> _logger;
private readonly IProcessInvoker _processInvoker;

private readonly string _rcBinaryPath = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
"Desktop",
EnvironmentHelper.DesktopExecutableFileName);

public AppLauncherLinux(
IConfigService configService,
Expand All @@ -34,78 +38,6 @@ public AppLauncherLinux(
}


private int StartLinuxDesktopApp(string args)
{
var xauthority = GetXorgAuth();

var display = ":0";
var whoString = _processInvoker.InvokeProcessOutput("who", "")?.Trim();
var username = "";

if (!string.IsNullOrWhiteSpace(whoString))
{
try
{
var whoLine = whoString
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.First();

var whoSplit = whoLine.Split(' ', StringSplitOptions.RemoveEmptyEntries);
username = whoSplit[0];
display = whoSplit.Last().TrimStart('(').TrimEnd(')');
xauthority = $"/home/{username}/.Xauthority";
args = $"-u {username} {args}";
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while getting current X11 user.");
}
}

var psi = new ProcessStartInfo()
{
FileName = "sudo",
Arguments = args
};

psi.Environment.Add("DISPLAY", display);
psi.Environment.Add("XAUTHORITY", xauthority);
_logger.LogInformation(
"Attempting to launch screen caster with username {username}, xauthority {xauthority}, display {display}, and args {args}.",
username,
xauthority,
display,
args);
return Process.Start(psi)?.Id ?? throw new InvalidOperationException("Failed to launch desktop app.");
}

private string GetXorgAuth()
{
try
{
var processes = _processInvoker.InvokeProcessOutput("ps", "-eaf")?.Split(Environment.NewLine);
if (processes?.Length > 0)
{
var xorgLine = processes.FirstOrDefault(x => x.Contains("xorg", StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrWhiteSpace(xorgLine))
{
var xorgSplit = xorgLine?.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList();
var authIndex = xorgSplit?.IndexOf("-auth");
if (authIndex > -1 && xorgSplit?.Count >= authIndex + 1)
{
var auth = xorgSplit[(int)authIndex + 1];
if (!string.IsNullOrWhiteSpace(auth))
{
return auth;
}
}
}
}
}
catch { }
return string.Empty;
}

public async Task<int> LaunchChatService(string pipeName, string userConnectionId, string requesterName, string orgName, string orgId, HubConnection hubConnection)
{
try
Expand Down Expand Up @@ -197,4 +129,124 @@ public async Task RestartScreenCaster(string[] viewerIds, string sessionId, stri
throw;
}
}

private int StartLinuxDesktopApp(string args)
{
var xdisplay = ":0";
var xauthority = string.Empty;

var xResult = TryGetXAuth("Xorg");

if (!xResult.IsSuccess)
{
// If running Wayland, this still ends up still being unusable.
// This X server will only provide a black screen with any apps
// launched within the display it's using, but the display won't
// show anything being rendered by the Wayland compositor. It's
// better than simply crashing, though, so I'll leave it here
// until Wayland support is added.
xResult = TryGetXAuth("Xwayland");
}

if (xResult.IsSuccess)
{
xdisplay = xResult.Value.XDisplay;
xauthority = xResult.Value.XAuthority;
}
else
{
_logger.LogError("Failed to get X server auth.");
}

var whoString = _processInvoker.InvokeProcessOutput("who", "")?.Trim();
var username = "";

if (!string.IsNullOrWhiteSpace(whoString))
{
try
{
var whoLines = whoString.Split('\n', StringSplitOptions.RemoveEmptyEntries);

var whoLine = whoLines.FirstOrDefault(x =>
Regex.IsMatch(
x.Split(" ", StringSplitOptions.RemoveEmptyEntries).Last(),
@"\(:[\d]*\)"));

if (!string.IsNullOrWhiteSpace(whoLine))
{
var whoSplit = whoLine.Split(' ', StringSplitOptions.RemoveEmptyEntries);
username = whoSplit[0];
xdisplay = whoSplit.Last().TrimStart('(').TrimEnd(')');
xauthority = $"/home/{username}/.Xauthority";
args = $"-u {username} {args}";

}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while getting current X11 user.");
}
}

var psi = new ProcessStartInfo()
{
FileName = "sudo",
Arguments = args
};

psi.Environment.Add("DISPLAY", xdisplay);
psi.Environment.Add("XAUTHORITY", xauthority);
_logger.LogInformation(
"Attempting to launch screen caster with username {username}, xauthority {xauthority}, display {display}, and args {args}.",
username,
xauthority,
xdisplay,
args);
return Process.Start(psi)?.Id ?? throw new InvalidOperationException("Failed to launch desktop app.");
}

private Result<XAuthInfo> TryGetXAuth(string xServerProcess)
{
try
{
var xdisplay = ":0";
var xauthority = string.Empty;

var xprocess = _processInvoker
.InvokeProcessOutput("ps", $"-C {xServerProcess} -f")
?.Split(Environment.NewLine)
?.FirstOrDefault(x => x.Contains(" -auth "));

if (string.IsNullOrWhiteSpace(xprocess))
{
_logger.LogInformation("{xServerProcess} process not found.", xServerProcess);
return Result.Fail<XAuthInfo>($"{xServerProcess} process not found.");
}

_logger.LogInformation("Resolved X server process: {xprocess}", xprocess);

var xprocSplit = xprocess
.Split(" ", StringSplitOptions.RemoveEmptyEntries)
.ToList();

var xProcIndex = xprocSplit.IndexWhere(x => x.EndsWith(xServerProcess));
if (xProcIndex > -1 && xprocSplit[xProcIndex + 1].StartsWith(":"))
{
xdisplay = xprocSplit[xProcIndex + 1];
}

var authIndex = xprocSplit.IndexOf("-auth");
if (authIndex > -1)
{
xauthority = xprocSplit[authIndex + 1];
}
return Result.Ok(new XAuthInfo(xdisplay, xauthority));
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while geting X auth for {xServerProcess}.", xServerProcess);
return Result.Fail<XAuthInfo>($"Error while geting X auth for {xServerProcess}.");
}
}
private record XAuthInfo(string XDisplay, string XAuthority);
}
14 changes: 14 additions & 0 deletions Shared/Extensions/IEnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,18 @@ public static async IAsyncEnumerable<T> ToAsyncEnumerable<T>(this IEnumerable<T>
await Task.Yield();
}
}

public static int IndexWhere<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
var index = 0;
foreach (var item in source)
{
if (predicate(item))
{
return index;
}
index++;
}
return -1;
}
}

0 comments on commit 55f3a80

Please sign in to comment.