Загрузка данных
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
public static class KWriteLauncher
{
[DllImport("libc")]
private static extern uint geteuid();
public static void OpenKWriteAsOriginalUser(string filePath)
{
if (string.IsNullOrWhiteSpace(filePath))
throw new ArgumentException("Путь к файлу не указан.", nameof(filePath));
string fullPath = Path.GetFullPath(filePath);
if (!File.Exists(fullPath))
throw new FileNotFoundException("Файл не найден.", fullPath);
bool isRoot = geteuid() == 0;
// Если приложение НЕ запущено через sudo/root — открываем обычно.
if (!isRoot)
{
StartDetached("kwrite", fullPath);
return;
}
string? sudoUser = Environment.GetEnvironmentVariable("SUDO_USER");
string? sudoUid = Environment.GetEnvironmentVariable("SUDO_UID");
if (string.IsNullOrWhiteSpace(sudoUser) || string.IsNullOrWhiteSpace(sudoUid))
{
throw new InvalidOperationException(
"Приложение запущено от root, но SUDO_USER/SUDO_UID не найдены. " +
"Нужно явно знать пользователя графической сессии."
);
}
string home = GetHomeDirectory(sudoUser)
?? throw new InvalidOperationException($"Не удалось определить HOME для пользователя {sudoUser}.");
string runtimeDir = $"/run/user/{sudoUid}";
string dbusAddress = $"unix:path={runtimeDir}/bus";
if (!Directory.Exists(runtimeDir))
{
throw new InvalidOperationException(
$"Не найден {runtimeDir}. Пользователь {sudoUser} должен быть залогинен в графическую сессию."
);
}
string display = Environment.GetEnvironmentVariable("DISPLAY") ?? ":0";
string? waylandDisplay = Environment.GetEnvironmentVariable("WAYLAND_DISPLAY");
string? xAuthority = Environment.GetEnvironmentVariable("XAUTHORITY");
if (string.IsNullOrWhiteSpace(xAuthority))
{
string defaultXauthority = Path.Combine(home, ".Xauthority");
if (File.Exists(defaultXauthority))
xAuthority = defaultXauthority;
}
var psi = new ProcessStartInfo
{
FileName = "setsid",
UseShellExecute = false,
CreateNoWindow = true
};
// setsid -f — отделяет процесс, чтобы ваше приложение точно не зависело от KWrite.
psi.ArgumentList.Add("-f");
// runuser — запускает команду от имени обычного пользователя.
psi.ArgumentList.Add("runuser");
psi.ArgumentList.Add("-u");
psi.ArgumentList.Add(sudoUser);
psi.ArgumentList.Add("--");
// env — задаем корректное окружение графической сессии пользователя.
psi.ArgumentList.Add("env");
psi.ArgumentList.Add($"HOME={home}");
psi.ArgumentList.Add($"USER={sudoUser}");
psi.ArgumentList.Add($"LOGNAME={sudoUser}");
psi.ArgumentList.Add($"XDG_RUNTIME_DIR={runtimeDir}");
psi.ArgumentList.Add($"DBUS_SESSION_BUS_ADDRESS={dbusAddress}");
psi.ArgumentList.Add($"DISPLAY={display}");
if (!string.IsNullOrWhiteSpace(waylandDisplay))
psi.ArgumentList.Add($"WAYLAND_DISPLAY={waylandDisplay}");
if (!string.IsNullOrWhiteSpace(xAuthority))
psi.ArgumentList.Add($"XAUTHORITY={xAuthority}");
psi.ArgumentList.Add("kwrite");
psi.ArgumentList.Add(fullPath);
Process? process = Process.Start(psi);
// Не вызываем WaitForExit().
process?.Dispose();
}
private static void StartDetached(string command, string argument)
{
var psi = new ProcessStartInfo
{
FileName = "setsid",
UseShellExecute = false,
CreateNoWindow = true
};
psi.ArgumentList.Add("-f");
psi.ArgumentList.Add(command);
psi.ArgumentList.Add(argument);
Process? process = Process.Start(psi);
process?.Dispose();
}
private static string? GetHomeDirectory(string userName)
{
foreach (string line in File.ReadLines("/etc/passwd"))
{
if (line.StartsWith(userName + ":", StringComparison.Ordinal))
{
string[] parts = line.Split(':');
if (parts.Length >= 6)
return parts[5];
}
}
return null;
}
}