qemu-harness.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. #!/usr/bin/env python3
  2. """
  3. Harness for running QEMU and communicating window sizes through serial.
  4. """
  5. import subprocess
  6. import asyncio
  7. import time
  8. import sys
  9. from Xlib.display import Display
  10. from Xlib.protocol.event import KeyPress, KeyRelease
  11. from Xlib.XK import string_to_keysym
  12. import Xlib
  13. qemu_bin = 'qemu-system-i386'
  14. qemu = subprocess.Popen([
  15. qemu_bin,
  16. '-enable-kvm',
  17. '-cdrom','image.iso',
  18. # 1GB of RAM
  19. '-m','1G',
  20. # Enable audio
  21. '-soundhw','ac97,pcspk',
  22. # The GTK interface does not play well, force SDL
  23. '-display', 'sdl',
  24. # /dev/ttyS0 is stdio multiplexed with monitor
  25. '-serial', 'mon:stdio',
  26. # /dev/ttyS1 is TCP connection to the harness
  27. '-serial','tcp::4444,server,nowait',
  28. # Add a VGA card with 32mb of video RAM
  29. '-device', 'VGA,id=video0,vgamem_mb=32',
  30. # Set the fwcfg flag so our userspace app recognizes us
  31. '-fw_cfg','name=opt/org.toaruos.displayharness,string=1',
  32. # Boot directly to graphical mode
  33. '-fw_cfg','name=opt/org.toaruos.bootmode,string=normal'
  34. ])
  35. # Give QEMU some time to start up and create a window.
  36. time.sleep(1)
  37. # Find the QEMU window...
  38. def findQEMU(window):
  39. try:
  40. x = window.get_wm_name()
  41. if 'QEMU' in x:
  42. return window
  43. except:
  44. pass
  45. children = window.query_tree().children
  46. for w in children:
  47. x = findQEMU(w)
  48. if x: return x
  49. return None
  50. display = Display()
  51. root = display.screen().root
  52. qemu_win = findQEMU(root)
  53. def send_key(key, state, up=False):
  54. """Send a key press or release to the QEMU window."""
  55. time.sleep(0.1)
  56. t = KeyPress
  57. if up:
  58. t = KeyRelease
  59. sym = string_to_keysym(key)
  60. ke = t(
  61. time=int(time.time()),
  62. root=display.screen().root,
  63. window=qemu_win,
  64. same_screen=0,
  65. child=Xlib.X.NONE,
  66. root_x = 0, root_y = 0, event_x = 0, event_y = 0,
  67. state = 0xc,
  68. detail = display.keysym_to_keycode(sym)
  69. )
  70. qemu_win.send_event(ke)
  71. display.flush()
  72. class Client(asyncio.Protocol):
  73. def connection_made(self, transport):
  74. asyncio.ensure_future(heartbeat(transport))
  75. def data_received(self, data):
  76. if 'X' in data.decode('utf-8'):
  77. # Send Ctrl-Alt-u
  78. send_key('Control_L',0x00)
  79. send_key('Alt_L',0x04)
  80. send_key('u',0x0c)
  81. send_key('u',0x0c,True)
  82. send_key('Alt_L',0x0c,True)
  83. send_key('Control_L',0x04,True)
  84. async def heartbeat(transport):
  85. """Heartbeat process checks window size every second and sends update signal."""
  86. w = 0
  87. h = 0
  88. while 1:
  89. await asyncio.sleep(1)
  90. try:
  91. g = qemu_win.get_geometry()
  92. except Xlib.error.BadDrawable:
  93. print("QEMU window is gone, exiting.")
  94. asyncio.get_event_loop().call_soon(sys.exit, 0)
  95. return
  96. if g.width != w or g.height != h:
  97. transport.write(("geometry-changed %d %d\n" % (g.width,g.height)).encode('utf-8'))
  98. w = g.width
  99. h = g.height
  100. loop = asyncio.get_event_loop()
  101. coro = loop.create_connection(Client,'127.0.0.1',4444)
  102. asyncio.ensure_future(coro)
  103. loop.run_forever()
  104. loop.close()