update
This commit is contained in:
@@ -149,6 +149,7 @@ export class EcoDaemon {
|
|||||||
|
|
||||||
async getDisplays(): Promise<DisplayInfo[]> {
|
async getDisplays(): Promise<DisplayInfo[]> {
|
||||||
if (this.swayStatus.state !== 'running') {
|
if (this.swayStatus.state !== 'running') {
|
||||||
|
this.log(`[displays] Sway not running (state: ${this.swayStatus.state}), skipping display query`);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const uid = await this.getUserUid();
|
const uid = await this.getUserUid();
|
||||||
|
|||||||
@@ -28,11 +28,39 @@ export class ProcessManager {
|
|||||||
private user: string;
|
private user: string;
|
||||||
private swayProcess: Deno.ChildProcess | null = null;
|
private swayProcess: Deno.ChildProcess | null = null;
|
||||||
private browserProcess: Deno.ChildProcess | null = null;
|
private browserProcess: Deno.ChildProcess | null = null;
|
||||||
|
private swaySocket: string | null = null;
|
||||||
|
|
||||||
constructor(user: string) {
|
constructor(user: string) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the Sway IPC socket path in the runtime directory
|
||||||
|
* Sway creates sockets like: sway-ipc.$UID.$PID.sock
|
||||||
|
*/
|
||||||
|
async findSwaySocket(runtimeDir: string): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
for await (const entry of Deno.readDir(runtimeDir)) {
|
||||||
|
if (entry.name.startsWith('sway-ipc.') && entry.name.endsWith('.sock')) {
|
||||||
|
const socketPath = `${runtimeDir}/${entry.name}`;
|
||||||
|
console.log(`[sway] Found IPC socket: ${socketPath}`);
|
||||||
|
return socketPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[sway] Error finding socket: ${error}`);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSwaySocket(): string | null {
|
||||||
|
return this.swaySocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSwaySocket(socket: string | null): void {
|
||||||
|
this.swaySocket = socket;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate Sway configuration content for kiosk mode
|
* Generate Sway configuration content for kiosk mode
|
||||||
*/
|
*/
|
||||||
@@ -146,9 +174,19 @@ for_window [app_id="chromium-browser"] fullscreen enable
|
|||||||
* Run a swaymsg command to control Sway
|
* Run a swaymsg command to control Sway
|
||||||
*/
|
*/
|
||||||
async swaymsg(config: { runtimeDir: string; waylandDisplay: string }, command: string): Promise<boolean> {
|
async swaymsg(config: { runtimeDir: string; waylandDisplay: string }, command: string): Promise<boolean> {
|
||||||
|
// Find socket if not already found
|
||||||
|
if (!this.swaySocket) {
|
||||||
|
this.swaySocket = await this.findSwaySocket(config.runtimeDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.swaySocket) {
|
||||||
|
console.error('[swaymsg] No Sway IPC socket found');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const env: Record<string, string> = {
|
const env: Record<string, string> = {
|
||||||
XDG_RUNTIME_DIR: config.runtimeDir,
|
XDG_RUNTIME_DIR: config.runtimeDir,
|
||||||
WAYLAND_DISPLAY: config.waylandDisplay,
|
SWAYSOCK: this.swaySocket,
|
||||||
};
|
};
|
||||||
|
|
||||||
const envString = Object.entries(env)
|
const envString = Object.entries(env)
|
||||||
@@ -292,6 +330,7 @@ for_window [app_id="chromium-browser"] fullscreen enable
|
|||||||
// Process may already be dead
|
// Process may already be dead
|
||||||
}
|
}
|
||||||
this.swayProcess = null;
|
this.swayProcess = null;
|
||||||
|
this.swaySocket = null; // Reset socket so we find new one on restart
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,9 +350,19 @@ for_window [app_id="chromium-browser"] fullscreen enable
|
|||||||
* Get connected displays via swaymsg
|
* Get connected displays via swaymsg
|
||||||
*/
|
*/
|
||||||
async getDisplays(config: { runtimeDir: string; waylandDisplay: string }): Promise<DisplayInfo[]> {
|
async getDisplays(config: { runtimeDir: string; waylandDisplay: string }): Promise<DisplayInfo[]> {
|
||||||
|
// Find socket if not already found
|
||||||
|
if (!this.swaySocket) {
|
||||||
|
this.swaySocket = await this.findSwaySocket(config.runtimeDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.swaySocket) {
|
||||||
|
console.error('[displays] No Sway IPC socket found');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const env: Record<string, string> = {
|
const env: Record<string, string> = {
|
||||||
XDG_RUNTIME_DIR: config.runtimeDir,
|
XDG_RUNTIME_DIR: config.runtimeDir,
|
||||||
WAYLAND_DISPLAY: config.waylandDisplay,
|
SWAYSOCK: this.swaySocket,
|
||||||
};
|
};
|
||||||
|
|
||||||
const envString = Object.entries(env)
|
const envString = Object.entries(env)
|
||||||
@@ -329,7 +378,8 @@ for_window [app_id="chromium-browser"] fullscreen enable
|
|||||||
try {
|
try {
|
||||||
const result = await cmd.output();
|
const result = await cmd.output();
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
console.error('[displays] Failed to get outputs');
|
const stderr = new TextDecoder().decode(result.stderr);
|
||||||
|
console.error(`[displays] Failed to get outputs: ${stderr}`);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,6 +477,7 @@ for_window [app_id="chromium-browser"] fullscreen enable
|
|||||||
console.log(`[${name}] Process exited with code ${status.code}`);
|
console.log(`[${name}] Process exited with code ${status.code}`);
|
||||||
if (name === 'sway' && this.swayProcess === process) {
|
if (name === 'sway' && this.swayProcess === process) {
|
||||||
this.swayProcess = null;
|
this.swayProcess = null;
|
||||||
|
this.swaySocket = null; // Reset socket so we find new one on restart
|
||||||
} else if (name === 'chromium' && this.browserProcess === process) {
|
} else if (name === 'chromium' && this.browserProcess === process) {
|
||||||
this.browserProcess = null;
|
this.browserProcess = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export const VERSION = "0.3.9";
|
export const VERSION = "0.4.2";
|
||||||
|
|||||||
Binary file not shown.
@@ -48,7 +48,7 @@ else
|
|||||||
echo "KVM not available, using software emulation (slower)"
|
echo "KVM not available, using software emulation (slower)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Start QEMU with VirtIO-GPU (VirGL OpenGL acceleration) and serial console
|
# Start QEMU with multiple displays (3 total) for multi-monitor testing
|
||||||
> "$SERIAL_LOG" # Clear old log
|
> "$SERIAL_LOG" # Clear old log
|
||||||
qemu-system-x86_64 \
|
qemu-system-x86_64 \
|
||||||
$KVM_OPTS \
|
$KVM_OPTS \
|
||||||
@@ -57,7 +57,9 @@ qemu-system-x86_64 \
|
|||||||
-bios /usr/share/qemu/OVMF.fd \
|
-bios /usr/share/qemu/OVMF.fd \
|
||||||
-drive file="$ISO_PATH",media=cdrom \
|
-drive file="$ISO_PATH",media=cdrom \
|
||||||
-drive file="$DISK_PATH",format=qcow2,if=virtio \
|
-drive file="$DISK_PATH",format=qcow2,if=virtio \
|
||||||
-device virtio-vga \
|
-device virtio-gpu-pci,id=video0 \
|
||||||
|
-device virtio-gpu-pci,id=video1 \
|
||||||
|
-device virtio-gpu-pci,id=video2 \
|
||||||
-display none \
|
-display none \
|
||||||
-spice port=5930,disable-ticketing=on \
|
-spice port=5930,disable-ticketing=on \
|
||||||
-serial unix:"$SERIAL_SOCK",server,nowait \
|
-serial unix:"$SERIAL_SOCK",server,nowait \
|
||||||
|
|||||||
@@ -15,35 +15,57 @@ if [ ! -S "$MONITOR_SOCK" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$SCREENSHOT_DIR"
|
mkdir -p "$SCREENSHOT_DIR"
|
||||||
PPM_FILE="$SCREENSHOT_DIR/ecoos-$TIMESTAMP.ppm"
|
|
||||||
PNG_FILE="$SCREENSHOT_DIR/ecoos-$TIMESTAMP.png"
|
|
||||||
LATEST_FILE="$SCREENSHOT_DIR/latest.png"
|
|
||||||
|
|
||||||
echo "Taking screenshot..."
|
# Screenshot all displays (video0, video1, video2)
|
||||||
echo "screendump $PPM_FILE" | socat - UNIX-CONNECT:"$MONITOR_SOCK"
|
DISPLAYS="video0 video1 video2"
|
||||||
sleep 1
|
SCREENSHOTS=()
|
||||||
|
|
||||||
# Check if PPM was created
|
echo "Taking screenshots of all displays..."
|
||||||
if [ ! -f "$PPM_FILE" ]; then
|
|
||||||
echo "ERROR: Screenshot failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Convert to PNG if imagemagick is available
|
for DISPLAY in $DISPLAYS; do
|
||||||
if command -v convert &> /dev/null; then
|
PPM_FILE="$SCREENSHOT_DIR/ecoos-$TIMESTAMP-$DISPLAY.ppm"
|
||||||
convert "$PPM_FILE" "$PNG_FILE"
|
PNG_FILE="$SCREENSHOT_DIR/ecoos-$TIMESTAMP-$DISPLAY.png"
|
||||||
rm "$PPM_FILE"
|
|
||||||
|
|
||||||
# Copy to latest.png
|
# Take screenshot of this display
|
||||||
cp "$PNG_FILE" "$LATEST_FILE"
|
echo "screendump $PPM_FILE $DISPLAY" | socat - UNIX-CONNECT:"$MONITOR_SOCK" 2>/dev/null
|
||||||
|
sleep 0.5
|
||||||
|
|
||||||
echo "Screenshot saved: $PNG_FILE"
|
if [ -f "$PPM_FILE" ]; then
|
||||||
|
# Convert to PNG if imagemagick is available
|
||||||
|
if command -v convert &> /dev/null; then
|
||||||
|
convert "$PPM_FILE" "$PNG_FILE"
|
||||||
|
rm "$PPM_FILE"
|
||||||
|
SCREENSHOTS+=("$PNG_FILE")
|
||||||
|
echo " $DISPLAY: $PNG_FILE"
|
||||||
|
else
|
||||||
|
SCREENSHOTS+=("$PPM_FILE")
|
||||||
|
echo " $DISPLAY: $PPM_FILE"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " $DISPLAY: (no output or not connected)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Create a combined image if we have multiple screenshots and imagemagick
|
||||||
|
if command -v convert &> /dev/null && [ ${#SCREENSHOTS[@]} -gt 1 ]; then
|
||||||
|
COMBINED_FILE="$SCREENSHOT_DIR/ecoos-$TIMESTAMP-combined.png"
|
||||||
|
LATEST_FILE="$SCREENSHOT_DIR/latest.png"
|
||||||
|
|
||||||
|
# Combine images horizontally
|
||||||
|
convert "${SCREENSHOTS[@]}" +append "$COMBINED_FILE"
|
||||||
|
cp "$COMBINED_FILE" "$LATEST_FILE"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Combined screenshot: $COMBINED_FILE"
|
||||||
|
echo "Also saved as: $LATEST_FILE"
|
||||||
|
elif [ ${#SCREENSHOTS[@]} -eq 1 ]; then
|
||||||
|
LATEST_FILE="$SCREENSHOT_DIR/latest.png"
|
||||||
|
cp "${SCREENSHOTS[0]}" "$LATEST_FILE"
|
||||||
|
echo ""
|
||||||
echo "Also saved as: $LATEST_FILE"
|
echo "Also saved as: $LATEST_FILE"
|
||||||
else
|
|
||||||
echo "Screenshot saved: $PPM_FILE"
|
|
||||||
echo "(Install imagemagick to auto-convert to PNG)"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Keep only last 20 screenshots (excluding latest.png)
|
# Keep only last 20 screenshot sets (excluding latest.png)
|
||||||
cd "$SCREENSHOT_DIR"
|
cd "$SCREENSHOT_DIR"
|
||||||
ls -t ecoos-*.png 2>/dev/null | tail -n +21 | xargs -r rm -f
|
ls -t ecoos-*-combined.png 2>/dev/null | tail -n +21 | xargs -r rm -f
|
||||||
|
ls -t ecoos-*-video*.png 2>/dev/null | tail -n +61 | xargs -r rm -f
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ecobridge/eco-os",
|
"name": "@ecobridge/eco-os",
|
||||||
"version": "0.4.1",
|
"version": "0.4.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "[ -z \"$CI\" ] && npm version patch --no-git-tag-version || true && node -e \"const v=require('./package.json').version; require('fs').writeFileSync('ecoos_daemon/ts/version.ts', 'export const VERSION = \\\"'+v+'\\\";\\n');\" && pnpm run daemon:bundle && cp ecoos_daemon/bundle/eco-daemon isobuild/config/includes.chroot/opt/eco/bin/ && mkdir -p .nogit/iso && docker build --no-cache -t ecoos-builder -f isobuild/Dockerfile . && docker run --privileged --name ecoos-build ecoos-builder && docker cp ecoos-build:/output/ecoos.iso .nogit/iso/ecoos.iso && docker rm ecoos-build",
|
"build": "[ -z \"$CI\" ] && npm version patch --no-git-tag-version || true && node -e \"const v=require('./package.json').version; require('fs').writeFileSync('ecoos_daemon/ts/version.ts', 'export const VERSION = \\\"'+v+'\\\";\\n');\" && pnpm run daemon:bundle && cp ecoos_daemon/bundle/eco-daemon isobuild/config/includes.chroot/opt/eco/bin/ && mkdir -p .nogit/iso && docker build --no-cache -t ecoos-builder -f isobuild/Dockerfile . && docker run --privileged --name ecoos-build ecoos-builder && docker cp ecoos-build:/output/ecoos.iso .nogit/iso/ecoos.iso && docker rm ecoos-build",
|
||||||
|
|||||||
Reference in New Issue
Block a user