fix(multi-display): fix runtime directory race condition and SPICE display enabling
- Fix tmpfs race condition in daemon by mounting runtime directory explicitly before starting Sway, preventing sockets from being hidden when systemd-logind mounts over them later - Fix enable-displays.py to use correct SpiceClientGLib API methods (update_display_enabled/update_display instead of set_display_enabled/set_display) - Fix virt-viewer monitor-mapping to use 1-indexed client monitors - Add virt-viewer config setup and automatic display enabling to test script - Multi-display now works correctly with 3 QXL devices
This commit is contained in:
@@ -305,12 +305,13 @@ export class EcoDaemon {
|
||||
|
||||
private async startSwayWithMode(mode: 'drm' | 'headless'): Promise<void> {
|
||||
const uid = await this.getUserUid();
|
||||
|
||||
// Ensure XDG_RUNTIME_DIR exists
|
||||
const gid = await this.getUserGid();
|
||||
const runtimeDir = `/run/user/${uid}`;
|
||||
await runCommand('mkdir', ['-p', runtimeDir]);
|
||||
await runCommand('chown', [`${this.config.user}:${this.config.user}`, runtimeDir]);
|
||||
await runCommand('chmod', ['700', runtimeDir]);
|
||||
|
||||
// Ensure XDG_RUNTIME_DIR exists as a proper tmpfs mount
|
||||
// This is critical - if Sway creates sockets before the tmpfs is mounted,
|
||||
// they become hidden when systemd-logind mounts the tmpfs later
|
||||
await this.ensureRuntimeDirTmpfs(runtimeDir, uid, gid);
|
||||
|
||||
if (mode === 'drm') {
|
||||
this.log('Starting Sway with DRM backend (hardware rendering)');
|
||||
@@ -374,6 +375,56 @@ export class EcoDaemon {
|
||||
return parseInt(result.stdout.trim(), 10);
|
||||
}
|
||||
|
||||
private async getUserGid(): Promise<number> {
|
||||
const result = await runCommand('id', ['-g', this.config.user]);
|
||||
if (!result.success) {
|
||||
throw new Error('Failed to get user GID: ' + result.stderr);
|
||||
}
|
||||
return parseInt(result.stdout.trim(), 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the user runtime directory exists as a proper tmpfs mount.
|
||||
* This prevents race conditions where Sway creates sockets before
|
||||
* systemd-logind mounts the tmpfs, causing sockets to be hidden.
|
||||
*/
|
||||
private async ensureRuntimeDirTmpfs(runtimeDir: string, uid: number, gid: number): Promise<void> {
|
||||
// Check if runtime dir is already a tmpfs mount
|
||||
const mountCheck = await runCommand('findmnt', ['-n', '-o', 'FSTYPE', runtimeDir]);
|
||||
if (mountCheck.success && mountCheck.stdout.trim() === 'tmpfs') {
|
||||
this.log(`Runtime directory ${runtimeDir} is already a tmpfs mount`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
await runCommand('mkdir', ['-p', runtimeDir]);
|
||||
|
||||
// Mount a tmpfs if not already mounted
|
||||
this.log(`Mounting tmpfs on ${runtimeDir}`);
|
||||
const mountResult = await runCommand('mount', [
|
||||
'-t', 'tmpfs',
|
||||
'-o', `mode=700,uid=${uid},gid=${gid},size=100M`,
|
||||
'tmpfs',
|
||||
runtimeDir
|
||||
]);
|
||||
|
||||
if (!mountResult.success) {
|
||||
// If mount fails, maybe it's already mounted by systemd-logind
|
||||
// Double-check and continue if it's now a tmpfs
|
||||
const recheckMount = await runCommand('findmnt', ['-n', '-o', 'FSTYPE', runtimeDir]);
|
||||
if (recheckMount.success && recheckMount.stdout.trim() === 'tmpfs') {
|
||||
this.log(`Runtime directory ${runtimeDir} was mounted by another process`);
|
||||
return;
|
||||
}
|
||||
this.log(`Warning: Failed to mount tmpfs on ${runtimeDir}: ${mountResult.stderr}`);
|
||||
// Fall back to just ensuring the directory exists with correct permissions
|
||||
await runCommand('chown', [`${uid}:${gid}`, runtimeDir]);
|
||||
await runCommand('chmod', ['700', runtimeDir]);
|
||||
} else {
|
||||
this.log(`Successfully mounted tmpfs on ${runtimeDir}`);
|
||||
}
|
||||
}
|
||||
|
||||
private startJournalReader(): void {
|
||||
(async () => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user