From 0539d183b184e78ccee7e023fcfa1cd7ec45892a Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Fri, 9 Jan 2026 23:28:33 +0000 Subject: [PATCH] update --- ecoos_daemon/ts/version.ts | 2 +- ecoos_daemon/vdagent/eco-vdagent.py | 448 ++++++++++++++++++ .../normal/0100-enable-services.hook.chroot | 3 + .../etc/systemd/system/eco-vdagent.service | 16 + .../eco-vdagent.service | 1 + .../includes.chroot/opt/eco/bin/eco-daemon | Bin 87003557 -> 87002825 bytes .../includes.chroot/opt/eco/bin/eco-vdagent | 448 ++++++++++++++++++ .../live-build/package-lists/base.list.chroot | 1 + .../package-lists/drivers.list.chroot | 1 + isotest/enable-displays.py | 128 +++++ isotest/run-test.sh | 12 +- isotest/virt-viewer-settings | 5 + package.json | 2 +- 13 files changed, 1061 insertions(+), 6 deletions(-) create mode 100644 ecoos_daemon/vdagent/eco-vdagent.py create mode 100644 isobuild/config/includes.chroot/etc/systemd/system/eco-vdagent.service create mode 120000 isobuild/config/includes.chroot/etc/systemd/system/multi-user.target.wants/eco-vdagent.service create mode 100755 isobuild/config/includes.chroot/opt/eco/bin/eco-vdagent create mode 100755 isotest/enable-displays.py create mode 100644 isotest/virt-viewer-settings diff --git a/ecoos_daemon/ts/version.ts b/ecoos_daemon/ts/version.ts index 86305df..0fdd7d6 100644 --- a/ecoos_daemon/ts/version.ts +++ b/ecoos_daemon/ts/version.ts @@ -1 +1 @@ -export const VERSION = "0.4.4"; +export const VERSION = "0.4.11"; diff --git a/ecoos_daemon/vdagent/eco-vdagent.py b/ecoos_daemon/vdagent/eco-vdagent.py new file mode 100644 index 0000000..f1ddda3 --- /dev/null +++ b/ecoos_daemon/vdagent/eco-vdagent.py @@ -0,0 +1,448 @@ +#!/usr/bin/env python3 +""" +EcoOS Wayland Display Agent (eco-vdagent) + +A Wayland-native replacement for spice-vdagent that uses swaymsg/wlr-output-management +instead of xrandr to configure displays. + +Listens on the SPICE virtio-serial port for VD_AGENT_MONITORS_CONFIG messages +and applies the configuration to Sway outputs. +""" + +import os +import sys +import struct +import subprocess +import json +import time +import signal +import logging +from pathlib import Path + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - eco-vdagent - %(levelname)s - %(message)s' +) +log = logging.getLogger('eco-vdagent') + +# SPICE VDAgent Protocol Constants +VD_AGENT_PROTOCOL = 1 + +# Message types +VD_AGENT_MOUSE_STATE = 1 +VD_AGENT_MONITORS_CONFIG = 2 +VD_AGENT_REPLY = 3 +VD_AGENT_CLIPBOARD = 4 +VD_AGENT_DISPLAY_CONFIG = 5 +VD_AGENT_ANNOUNCE_CAPABILITIES = 6 +VD_AGENT_CLIPBOARD_GRAB = 7 +VD_AGENT_CLIPBOARD_REQUEST = 8 +VD_AGENT_CLIPBOARD_RELEASE = 9 +VD_AGENT_FILE_XFER_START = 10 +VD_AGENT_FILE_XFER_STATUS = 11 +VD_AGENT_FILE_XFER_DATA = 12 +VD_AGENT_CLIENT_DISCONNECTED = 13 +VD_AGENT_MAX_CLIPBOARD = 14 +VD_AGENT_AUDIO_VOLUME_SYNC = 15 +VD_AGENT_GRAPHICS_DEVICE_INFO = 16 + +# Reply error codes +VD_AGENT_SUCCESS = 1 +VD_AGENT_ERROR = 2 + +# Capability bits +VD_AGENT_CAP_MOUSE_STATE = 0 +VD_AGENT_CAP_MONITORS_CONFIG = 1 +VD_AGENT_CAP_REPLY = 2 +VD_AGENT_CAP_CLIPBOARD = 3 +VD_AGENT_CAP_DISPLAY_CONFIG = 4 +VD_AGENT_CAP_CLIPBOARD_BY_DEMAND = 5 +VD_AGENT_CAP_CLIPBOARD_SELECTION = 6 +VD_AGENT_CAP_SPARSE_MONITORS_CONFIG = 7 +VD_AGENT_CAP_GUEST_LINEEND_LF = 8 +VD_AGENT_CAP_GUEST_LINEEND_CRLF = 9 +VD_AGENT_CAP_MAX_CLIPBOARD = 10 +VD_AGENT_CAP_AUDIO_VOLUME_SYNC = 11 +VD_AGENT_CAP_MONITORS_CONFIG_POSITION = 12 +VD_AGENT_CAP_FILE_XFER_DISABLED = 13 +VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS = 14 +VD_AGENT_CAP_GRAPHICS_DEVICE_INFO = 15 +VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB = 16 +VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL = 17 + +# Virtio serial port path +VIRTIO_PORT = '/dev/virtio-ports/com.redhat.spice.0' + +# VDI Chunk header: port(4) + size(4) = 8 bytes +VDI_CHUNK_HEADER_SIZE = 8 +VDI_CHUNK_HEADER_FMT = ' len(data): + log.error(f"Truncated monitor config at index {i}") + break + + height, width, depth, x, y = struct.unpack( + MON_CONFIG_FMT, + data[offset:offset + MON_CONFIG_SIZE] + ) + + monitors.append({ + 'width': width, + 'height': height, + 'depth': depth, + 'x': x, + 'y': y + }) + log.info(f" Monitor {i}: {width}x{height}+{x}+{y} depth={depth}") + offset += MON_CONFIG_SIZE + + return monitors + + def send_reply(self, msg_type, error_code): + """Send VD_AGENT_REPLY message""" + # Reply data: type(4) + error(4) = 8 bytes + reply_data = struct.pack(' 0: + try: + os.write(self.port_fd, message) + return True + except OSError as e: + if e.errno == 11: # EAGAIN - resource temporarily unavailable + retries -= 1 + time.sleep(0.1) + continue + log.error(f"Failed to send message type {msg_type}: {e}") + return False + log.error(f"Failed to send message type {msg_type}: EAGAIN after retries") + return False + + def announce_capabilities(self): + """Send VD_AGENT_ANNOUNCE_CAPABILITIES to register with SPICE server""" + # Build capability bits - we support monitors config + caps = 0 + caps |= (1 << VD_AGENT_CAP_MONITORS_CONFIG) + caps |= (1 << VD_AGENT_CAP_REPLY) + caps |= (1 << VD_AGENT_CAP_SPARSE_MONITORS_CONFIG) + caps |= (1 << VD_AGENT_CAP_MONITORS_CONFIG_POSITION) + + # VDAgentAnnounceCapabilities: request(4) + caps(4) = 8 bytes + # request=1 means we want the server to send us its capabilities + announce_data = struct.pack('vqItB4meDF&N1JFH?II`IM{ab8j?pO&h|bX^ zy2gRgEmDye-J?h3M?n-uQ4~iyN}@E%qC9#=ujm~IMV~l04vC8B8~vhx42Xd-C=QLo z;_x^kj*P)^R1ArsF)WUbW8&C2E{>1kaYCFJC&h?3IY!1QacZ0vqhfT7iLo&*PLJ_1 zAtuHdab`@4$x#_oVroo_=`ka!VrHBbv*PTS9dlxCoD=in+?XE=Vqu&Y=SRc^aba8( zi(+wH9G3)fX)KAQaamj*%i@Yy9#_Uyadli1*T#ytF0PN2aYJO{#<(eNj$7i^xGipv zJL1l`EAEba;@-F~?vDrJ!FVVhjz{9rcq|@|Rq;eT8LQ)|SQAgj+E^FQ#IvzJo{Q(> zg?KSuikD+UY>Z8@Ikv>s*cPwEtMOXA9&f~(@m9PY@5HIij(dP04nfzVLcUuYyW7Mci6g=Ru?p@q;=XeG23+6Zlh zc0!KOUdR0I8``J7$uAr#t37Dal+}scwvGtQ8+_5Q?ZO?x zox)wh-NHS>y~2IM{lWvngTh0?!@?uNqrzjtx-WRqD z9|${y4~3n=N5aR#C&Dh_Q{gk=bKwi&OW`ZwYvCJVx3EX}R`^c%Uf3(_6MhhW6n+wZ z7JdamwN-d?fQb(z))Kls!4U~q;{z@aI zvC>3osx(uYD=n0kN-L$c(ne{kv{Q1F_DZhOLFuS;QVvi$D_xYX%7IEZC8gvk-IX3n zzEYqRDn&}Ml2%HTQl(5OS9&VFl-|lgN+0E58tcp`YQvJfyyA|P~|Y?aODW) zNM*2clrlsasti+(R*q4QRgP1RSB5JmC?_f>DI=7Vm66IR%Bjj}$|z;DGDaDzj8jfm z#w!z)iOLzunaU((vQnu`QKl->lFT%w>{sw`2KDwipjE6bECl;z5m%2mqM$~DTh$_nK=<$7hM za)XjlZd7hkZdPtlZdGnmZddM5?o{ql?pE$m?p5wn?pGdA9#kGu9#$Sv9#tMw9#>W= zPbg0+tCgpeHOkYlp1A5xzW?;W%M=Jv~i4atZ|%iyfNH3!8p-4$rxdr zY>YHcF-|p3Ge#MsjWNbpW1MliG2WP9Of=3g&NL<&lZ{GaiZRugW=uC`7*)nh<1AyA zakeqrm}AT}&N1d0=Nj{k1;#?-JmY*Lj0=nljf;#$#$w}Q;}QeoQe%m+)VR#J+*oE@ zVJtVUG_EqPHm)(QHC7nc8P^*tjT?-NaiejQakFuYajS8gal3Jcai?*Yakp`gaj$Wo zali3^@u2aL@v!lT@u=~b@wlP`)(rc=wQ?bLDVI`y3TP6MZ*v%k~GY3wv{nmWy#=1vQz zrPIo3?X+>)I_;bsr@fQwbZ|O4oty)l&Q2Gnt8<{!%}F_VPIsq=lkXHbg-(%E?4+F% zr_?EP%AKB0FQ>P2kkiLG*g3?haQZs^oc_)LXP`64In+7KIovtIIno*I9OVpghC0KX zqn%@%W1ZuiMv+I%hevoU@(T&Kzg1bB;65IoFx*EN~V&=Q-y);auQc=v?G1 zauz!mJC`^(mpV(FrOsu}<<2ta3TL@D=ICoEx2+oSU6n zoLimSoZFo{oI9PnoV%TSoO_-7oco;zoClqUoQIu9oJXC+t&ezU2&TeOq^R4rp^S!gz z+2{P={OJ7T{OtVV{ObJX{OI544GvL=-bT`!9>!`7#g9*s&%N{jOg3d#yg%F^ZeMd|W_f_2NY$g$O{ zm81&`$_q1lHl&imenn;Z>5-}2nYlfyrq3Lg%AK6sb40p0U0jeVC@d-|$wD)-m6DS3 z{L)luSwT^@P?g)WAS+*9R+26%EiEq3jMAWdBw&xcWmt^K=)8@@e z%hH9Z!qVc>vVZ9pl%|V{@-z2m(c$@}<;A7xRJ!mVmH)L?L0Nu&enG0JtnlBoviz)L zCcQb8^y!vgT2PuQ`e(oYCjHl<|FE*&oQq%S&#qUQP%x4%HnpJVSDqE#+jD8v;Wa_a7D%7UV|(8&FPma z>{n4y*zcc5QRbOlc}Y%Yd$y&6P8g8cIV3M>naSCcmkeq;t#3uew7#hU6%_+g|7I8T y`yVYWmafh=u3NpQd$R53HU$N_y=sjus?48MTsgMw(EawiL-*Tn;pS|}p8o*V*F&}d delta 5059 zcmeH_Wtf#!*T-FPgLF#@LrBOOm@{*x0AnacL_o1cC1t2VW(F`2W2uNg#cn(*B6hb3 zib*JTBZ@6{x6grBpSs?U@5gsv_qDHeul0|$*WPQEtn9UH>B?RwhJ=QNM-p|SZfqa* zqJA`phS4Y*N0Vq8&7ygjEqq+I`)nIVodBG2gHGKP#heG#G!FmjE%$Nh&VFF#rT*I6Jt_Lj-z5q zRL0bp7Sm%!%#5m-6-USHsE(SbjX5zl=EX5_Y#bNI$NV@U7Q~5hQk)#8M8v6aTAUsW zJQxqfig-9y#;SNE9*x!USUet2 z#FOz&*O{uGQNthSXecxi8VgN?rb08JxzIvrDYO!H5LydugtkIEp}o*S*iq;x zbQ1D}&O#TVtI$o@N$4*05PAx|gq?-nLQ2RN`Uri60wFCF3PnOjC>BbDQlU&J7y1eP zg#p4Y!mh$@!tO$aFi;pI3>Jn6LxnwrJ%wSyUczu;Z()S6k1$dgC5#sK74{Rx2>S~M z2nPxW2?q;@2!{%X31fxBg(HL`g>k}oVS+GGm?TUVjuNH_mBLhEnlN3MAcaJsNiI73(@oGBoj zC7dlR7S0jQ70wgR7cLMk6fP1j7A_Gk6)qDl7p@SN2v-VO;VR*3;TmD7aILURxK3Cu zTrb=p+$h{6+$`K8+$!8A+%DW9+$r28+%4QA+$-EC+%G&JJSaRQtPma+Rtl?xM}$X( z)xu-KRhgztS7s zDJLtZD50FHoTi+vEL6@=7Aa>cC}$~WD~pwLlyjByl=GDflna%Ml#7*1luMP%l*^SX zlqJfQN>;f_xmvkKS*l#CEK{yimMhmQHz+qMHz_wOw(@Q`RfbD9`9b+n`APX%`9=9v`Azv<`9t|r7aC%yVTK!tQOBrj zY;V*v>KhG=hDIZ!vC+h6YBV#N8!e2MMk`|nqqWh-Xlt}H+8Z5=9gU7gCnL}3Y;-ZY z8r_VYjP6Dcqo>i!*xBf9q>Ox{kI~mCFw#b$QDkI{Vxz<;HOh=~qo2{=7+~yT>}u?0 z>~2&T1C2q(U}K0e)Y!w=(->y#Wehj=Hbxlx7$c2Q#%N<-V?SezvA=PEaiDRKajaG1fTTIKnv67-x((CKwZqNycR3C}WCIX-qYy8Pkm!#!REim}MMo%r>fx8l%>j zW6U+?8OIpM8pj#O8}p45j0MJt#!1G>#wkV^ry8djryC26GmJ&XnFhvL#@WVV;~e8$ z<2>Vh;{xMC<09i?;}YXi<1*uN;|gPmaix(pt}?DRt}&Jx*BZ-=>x|{b^~MdxjmAyJ z&BiUpt;TJ}?ZzF(oyJ|p-Nrq}y~cgU{l){vgT_O~3gcm8rLoF*#CX(LZ9HZ?ZaiT; zX*^{-ZLBfY8taVp#xusV#s=d#<9Xu+<3-~o<7ML&<5lA|<8|W=<4xl&W25o5vB`MH zc-Po$yl1>`d|-TNd}MrVd}4fRY%xAFJ~zHFzBIlvzBaxwzBRrxzBhg_el&hEel~tF zel>nGemDLw{?=PU6&Y>N?vy^_==n1E-b2WLm8qtnUBb2>X+oUTqcXD6q-)5GcM^m2A~dOIm6-|6G@bqbucQ|J^q z8K>AOaY~&sr`+l1^mhh0yEwZ#yE(f%70y6skTcjB;tX~6aQ1YDIeR(7oxPnA&OXja zXOuJA+1J_68RP8l9N--29ONAA9O4}69OjI54tI`lj&#O3=v8oq5hN&auvM&hgHC=LBbgbE0#SbFy=a6V9p5 zY0l}+Lgx%;k#nYlbCz?qv)DPuIoCPQIp4X!xzM@Dx!AeHxzxGLx!k$JS>jyjWSy&= ztDS3{rOvg^GUqyHxpTd9gL9*ElXJ6ki*u`Un{&H!hjXWMmvgssk8`hcpL4(Sfb*d9 zkh8*h*jeeUavpIWbyho%IgdL}I8QoHIZr!loVCt6XT9@`^Q^PMdCqy>dBJ(ndC7U% zdBu6vdChsiqjA37g7A3L8opE_Hd&z#SlFPtx( zubi))Z=7$P@0{8n^%CkQ zG)QQe&?upCLX(813C$9kC$vatnb0a>hlJJ%Z4%lhv`c88&>>;RgpLWF67mu{Cv-{Z zn$Rs_r-beaJra5*^h(${p?9((m5f>~i?X9vr;@%c)1{@QWtpOalA`jmg5vT_QE_3} zs`GQDF?H({rHcy+(%H|~q>{oxnS%17v8gV#UHa9`uAP+XGNVhsLyI$Ih2^PqVM%c& zSE|lsN=h?jMXA#AlJZ=lrc1wcX-R2$S#fDex~QNmJAQ2{>EXqh;==4%xv+Jsl9KX* zRC%Vb^e=jONjhDceIQpGkW(!zNR?*Fi~rj-oi0eHi&G_;Ovzugbh@OtB%STPF4wlF z3rfqfC*;C5t;>qbGO5DSOljHQz|xX}lEUmQx$1!hWd(%=rK#e=tt$U$$9zte@RWw`Tn=xxH;o0 zr;i&yebP2;^wG|etvo7+qN z)8&8ZrgCy#&(8nT#J|+CoBH=j+Bcs)sk&-%)ALzGiHpNIWc?qqxngj>?1jc zhT9zaWPjN_O`2p!Rxgk`*=KW87?VBZviziD_L5fB(m4A}E|a&aGM8E2_y6bn_igYj*6;*@#kEj?lYjAeKmaVAupXTna&+v+h z;i_56<(CmS!%A9cJpshbsvkNxo-a)o;PM~Yu=|i@Mpsmf{%22p2mKqY?c$9&@T!el`Xn2!>5%Etzy8FcnFZ4` VGbeT&wcU2hb-V4xZpdx9^$+i0{=@(P diff --git a/isobuild/config/includes.chroot/opt/eco/bin/eco-vdagent b/isobuild/config/includes.chroot/opt/eco/bin/eco-vdagent new file mode 100755 index 0000000..f1ddda3 --- /dev/null +++ b/isobuild/config/includes.chroot/opt/eco/bin/eco-vdagent @@ -0,0 +1,448 @@ +#!/usr/bin/env python3 +""" +EcoOS Wayland Display Agent (eco-vdagent) + +A Wayland-native replacement for spice-vdagent that uses swaymsg/wlr-output-management +instead of xrandr to configure displays. + +Listens on the SPICE virtio-serial port for VD_AGENT_MONITORS_CONFIG messages +and applies the configuration to Sway outputs. +""" + +import os +import sys +import struct +import subprocess +import json +import time +import signal +import logging +from pathlib import Path + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - eco-vdagent - %(levelname)s - %(message)s' +) +log = logging.getLogger('eco-vdagent') + +# SPICE VDAgent Protocol Constants +VD_AGENT_PROTOCOL = 1 + +# Message types +VD_AGENT_MOUSE_STATE = 1 +VD_AGENT_MONITORS_CONFIG = 2 +VD_AGENT_REPLY = 3 +VD_AGENT_CLIPBOARD = 4 +VD_AGENT_DISPLAY_CONFIG = 5 +VD_AGENT_ANNOUNCE_CAPABILITIES = 6 +VD_AGENT_CLIPBOARD_GRAB = 7 +VD_AGENT_CLIPBOARD_REQUEST = 8 +VD_AGENT_CLIPBOARD_RELEASE = 9 +VD_AGENT_FILE_XFER_START = 10 +VD_AGENT_FILE_XFER_STATUS = 11 +VD_AGENT_FILE_XFER_DATA = 12 +VD_AGENT_CLIENT_DISCONNECTED = 13 +VD_AGENT_MAX_CLIPBOARD = 14 +VD_AGENT_AUDIO_VOLUME_SYNC = 15 +VD_AGENT_GRAPHICS_DEVICE_INFO = 16 + +# Reply error codes +VD_AGENT_SUCCESS = 1 +VD_AGENT_ERROR = 2 + +# Capability bits +VD_AGENT_CAP_MOUSE_STATE = 0 +VD_AGENT_CAP_MONITORS_CONFIG = 1 +VD_AGENT_CAP_REPLY = 2 +VD_AGENT_CAP_CLIPBOARD = 3 +VD_AGENT_CAP_DISPLAY_CONFIG = 4 +VD_AGENT_CAP_CLIPBOARD_BY_DEMAND = 5 +VD_AGENT_CAP_CLIPBOARD_SELECTION = 6 +VD_AGENT_CAP_SPARSE_MONITORS_CONFIG = 7 +VD_AGENT_CAP_GUEST_LINEEND_LF = 8 +VD_AGENT_CAP_GUEST_LINEEND_CRLF = 9 +VD_AGENT_CAP_MAX_CLIPBOARD = 10 +VD_AGENT_CAP_AUDIO_VOLUME_SYNC = 11 +VD_AGENT_CAP_MONITORS_CONFIG_POSITION = 12 +VD_AGENT_CAP_FILE_XFER_DISABLED = 13 +VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS = 14 +VD_AGENT_CAP_GRAPHICS_DEVICE_INFO = 15 +VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB = 16 +VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL = 17 + +# Virtio serial port path +VIRTIO_PORT = '/dev/virtio-ports/com.redhat.spice.0' + +# VDI Chunk header: port(4) + size(4) = 8 bytes +VDI_CHUNK_HEADER_SIZE = 8 +VDI_CHUNK_HEADER_FMT = ' len(data): + log.error(f"Truncated monitor config at index {i}") + break + + height, width, depth, x, y = struct.unpack( + MON_CONFIG_FMT, + data[offset:offset + MON_CONFIG_SIZE] + ) + + monitors.append({ + 'width': width, + 'height': height, + 'depth': depth, + 'x': x, + 'y': y + }) + log.info(f" Monitor {i}: {width}x{height}+{x}+{y} depth={depth}") + offset += MON_CONFIG_SIZE + + return monitors + + def send_reply(self, msg_type, error_code): + """Send VD_AGENT_REPLY message""" + # Reply data: type(4) + error(4) = 8 bytes + reply_data = struct.pack(' 0: + try: + os.write(self.port_fd, message) + return True + except OSError as e: + if e.errno == 11: # EAGAIN - resource temporarily unavailable + retries -= 1 + time.sleep(0.1) + continue + log.error(f"Failed to send message type {msg_type}: {e}") + return False + log.error(f"Failed to send message type {msg_type}: EAGAIN after retries") + return False + + def announce_capabilities(self): + """Send VD_AGENT_ANNOUNCE_CAPABILITIES to register with SPICE server""" + # Build capability bits - we support monitors config + caps = 0 + caps |= (1 << VD_AGENT_CAP_MONITORS_CONFIG) + caps |= (1 << VD_AGENT_CAP_REPLY) + caps |= (1 << VD_AGENT_CAP_SPARSE_MONITORS_CONFIG) + caps |= (1 << VD_AGENT_CAP_MONITORS_CONFIG_POSITION) + + # VDAgentAnnounceCapabilities: request(4) + caps(4) = 8 bytes + # request=1 means we want the server to send us its capabilities + announce_data = struct.pack(' 1 else 'spice://localhost:5930' + num_displays = int(sys.argv[2]) if len(sys.argv) > 2 else 3 + + enabler = SpiceDisplayEnabler(uri, num_displays) + success = enabler.run() + sys.exit(0 if success else 1) diff --git a/isotest/run-test.sh b/isotest/run-test.sh index 3098bb8..7cbfb61 100755 --- a/isotest/run-test.sh +++ b/isotest/run-test.sh @@ -55,6 +55,9 @@ cleanup() { if [ -n "$VIEWER_PID" ] && kill -0 "$VIEWER_PID" 2>/dev/null; then kill "$VIEWER_PID" 2>/dev/null || true fi + if [ -n "$TWM_PID" ] && kill -0 "$TWM_PID" 2>/dev/null; then + kill "$TWM_PID" 2>/dev/null || true + fi if [ -n "$XORG_PID" ] && kill -0 "$XORG_PID" 2>/dev/null; then kill "$XORG_PID" 2>/dev/null || true fi @@ -78,7 +81,9 @@ qemu-system-x86_64 \ -bios /usr/share/qemu/OVMF.fd \ -drive file="$ISO_PATH",media=cdrom \ -drive file="$DISK_PATH",format=qcow2,if=virtio \ - -device virtio-vga,max_outputs=3 \ + -device qxl-vga,id=video0,ram_size=67108864,vram_size=67108864,vgamem_mb=64 \ + -device qxl,id=video1,ram_size=67108864,vram_size=67108864,vgamem_mb=64 \ + -device qxl,id=video2,ram_size=67108864,vram_size=67108864,vgamem_mb=64 \ -display none \ -spice port=5930,disable-ticketing=on \ -device virtio-serial-pci \ @@ -163,10 +168,9 @@ if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then xrandr --output DUMMY1 --mode 1920x1080 --pos 1920x0 2>/dev/null || true xrandr --output DUMMY2 --mode 1920x1080 --pos 3840x0 2>/dev/null || true - echo "Headless X server started on :$XDISPLAY with 3 RandR monitors" - xrandr --listmonitors + echo "Headless X server started on :$XDISPLAY" - # Launch remote-viewer in fullscreen to use all monitors + # Launch remote-viewer in fullscreen to request all monitors remote-viewer --full-screen spice://localhost:5930 & VIEWER_PID=$! echo "remote-viewer running headlessly (PID: $VIEWER_PID)" diff --git a/isotest/virt-viewer-settings b/isotest/virt-viewer-settings new file mode 100644 index 0000000..6790846 --- /dev/null +++ b/isotest/virt-viewer-settings @@ -0,0 +1,5 @@ +[virt-viewer] +share-clipboard=true + +[fallback] +monitor-mapping=1:0;2:1;3:2 diff --git a/package.json b/package.json index e61a8bb..96aa707 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ecobridge/eco-os", - "version": "0.4.4", + "version": "0.4.11", "private": true, "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",