Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Commit 6999405

Browse files
committed
nanokvm: add image management
1 parent a33a309 commit 6999405

File tree

3 files changed

+402
-16
lines changed

3 files changed

+402
-16
lines changed

packages/jumpstarter-driver-nanokvm/README.md

Lines changed: 96 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@
88
- **Snapshot Capture**: Take screenshots of the video stream
99
- **Keyboard Control**: Send text and keystrokes via HID emulation
1010
- **Mouse Control**: Full mouse support via WebSocket
11-
- Absolute positioning (0-65535 coordinate system)
12-
- Relative movement
11+
- Absolute positioning (0.0-1.0 normalized coordinates)
12+
- Relative movement (0.0-1.0 normalized, where 1.0 = full screen)
1313
- Left/right/middle button clicks
1414
- Mouse wheel scrolling
15+
- **Image Management**: Virtual disk and CD-ROM control
16+
- Mount/unmount disk and CD-ROM images
17+
- Download images from URLs
18+
- Check mounted image status
19+
- Monitor download progress
1520
- **Device Management**: Get device info, reboot the NanoKVM
1621
- **Composite Driver**: Access all functionality through a unified interface
1722

@@ -79,7 +84,7 @@ The NanoKVM driver is a composite driver that provides three main interfaces:
7984
8085
```{eval-rst}
8186
.. autoclass:: jumpstarter_driver_nanokvm.client.NanoKVMClient()
82-
:members: get_info, reboot
87+
:members: get_info, reboot, mount_image, download_image, get_mounted_image, get_cdrom_status, is_image_download_enabled, get_image_download_status
8388
```
8489
8590
### NanoKVMVideoClient
@@ -151,11 +156,14 @@ j nanokvm hid reset
151156
#### Mouse Commands
152157

153158
```bash
154-
# Move mouse to absolute coordinates (0-65535, scaled to screen)
155-
j nanokvm hid mouse move 32768 32768 # Center of screen
159+
# Move mouse to absolute coordinates (0.0-1.0, where 0.0=top/left, 1.0=bottom/right)
160+
j nanokvm hid mouse move 0.5 0.5 # Center of screen
161+
j nanokvm hid mouse move 0.0 0.0 # Top-left corner
162+
j nanokvm hid mouse move 1.0 1.0 # Bottom-right corner
156163
157-
# Move mouse relatively (-127 to 127)
158-
j nanokvm hid mouse move-rel 50 50 # Move right and down
164+
# Move mouse relatively (-1.0 to 1.0, where 1.0 = full screen width/height)
165+
j nanokvm hid mouse move-rel 0.1 0.1 # Move right and down by 10% of screen
166+
j nanokvm hid mouse move-rel -0.2 0.0 # Move left by 20% of screen width
159167
160168
# Click at current position (default: left button)
161169
j nanokvm hid mouse click
@@ -164,7 +172,7 @@ j nanokvm hid mouse click
164172
j nanokvm hid mouse click --button right
165173
166174
# Click at specific coordinates
167-
j nanokvm hid mouse click --x 32768 --y 32768 --button left
175+
j nanokvm hid mouse click --x 0.5 --y 0.5 --button left
168176
169177
# Scroll (default: down 5 units)
170178
j nanokvm hid mouse scroll
@@ -176,6 +184,34 @@ j nanokvm hid mouse scroll --dy 5
176184
j nanokvm hid mouse scroll --dy -5
177185
```
178186

187+
### Image Management Commands
188+
189+
```bash
190+
# Mount a disk image
191+
j nanokvm image mount /data/myimage.img
192+
193+
# Mount a CD-ROM image
194+
j nanokvm image mount /data/installer.iso --cdrom
195+
196+
# Unmount current image
197+
j nanokvm image unmount
198+
199+
# Check mounted image status
200+
j nanokvm image status
201+
202+
# Check if mounted image is in CD-ROM mode
203+
j nanokvm image cdrom-status
204+
205+
# Download an image from URL
206+
j nanokvm image download https://example.com/image.iso
207+
208+
# Check if image downloads are enabled
209+
j nanokvm image download-enabled
210+
211+
# Check download progress
212+
j nanokvm image download-status
213+
```
214+
179215
### Example Session
180216

181217
```bash
@@ -185,6 +221,14 @@ jmp shell -l my=device
185221
# Inside the shell, use the commands
186222
j nanokvm info
187223
j nanokvm video snapshot my_screen.jpg
224+
225+
# Mount a CD-ROM image
226+
j nanokvm image mount /data/installer.iso --cdrom
227+
j nanokvm image status
228+
229+
# Control the mouse and keyboard
230+
j nanokvm hid mouse move 0.5 0.5
231+
j nanokvm hid mouse click
188232
j nanokvm hid paste "echo 'Hello from NanoKVM'\n"
189233
```
190234

@@ -216,17 +260,24 @@ nanokvm.hid.press_key("\t") # Tab
216260
### Mouse Control
217261

218262
```python
219-
# Move mouse to center of screen
220-
nanokvm.hid.mouse_move_abs(32768, 32768)
263+
# Move mouse to center of screen (normalized 0.0-1.0 coordinates)
264+
nanokvm.hid.mouse_move_abs(0.5, 0.5)
265+
266+
# Move to top-left corner
267+
nanokvm.hid.mouse_move_abs(0.0, 0.0)
268+
269+
# Move to bottom-right corner
270+
nanokvm.hid.mouse_move_abs(1.0, 1.0)
221271
222272
# Click left button
223273
nanokvm.hid.mouse_click("left")
224274
225275
# Click at specific coordinates
226-
nanokvm.hid.mouse_click("left", x=32768, y=16384)
276+
nanokvm.hid.mouse_click("left", x=0.5, y=0.25)
227277
228-
# Move mouse relatively
229-
nanokvm.hid.mouse_move_rel(50, 50) # Move right and down
278+
# Move mouse relatively (normalized -1.0 to 1.0, where 1.0 = full screen)
279+
nanokvm.hid.mouse_move_rel(0.1, 0.1) # Move right/down by 10% of screen
280+
nanokvm.hid.mouse_move_rel(-0.2, 0.0) # Move left by 20% of screen width
230281
231282
# Scroll up
232283
nanokvm.hid.mouse_scroll(0, 5)
@@ -235,6 +286,38 @@ nanokvm.hid.mouse_scroll(0, 5)
235286
nanokvm.hid.mouse_scroll(0, -5)
236287
```
237288

289+
### Image Management
290+
291+
```python
292+
# Mount a disk image
293+
nanokvm.mount_image("/data/myimage.img", cdrom=False)
294+
295+
# Mount a CD-ROM image
296+
nanokvm.mount_image("/data/installer.iso", cdrom=True)
297+
298+
# Unmount current image
299+
nanokvm.mount_image("")
300+
301+
# Get mounted image info
302+
file = nanokvm.get_mounted_image()
303+
if file:
304+
print(f"Mounted: {file}")
305+
is_cdrom = nanokvm.get_cdrom_status()
306+
print(f"Mode: {'CD-ROM' if is_cdrom else 'Disk'}")
307+
308+
# Download an image
309+
status = nanokvm.download_image("https://example.com/image.iso")
310+
print(f"Download: {status['status']}, File: {status['file']}")
311+
312+
# Check if downloads are enabled
313+
if nanokvm.is_image_download_enabled():
314+
print("Downloads are available")
315+
316+
# Monitor download progress
317+
status = nanokvm.get_image_download_status()
318+
print(f"Status: {status['status']}, Progress: {status['percentage']}")
319+
```
320+
238321
### Device Management
239322

240323
```python

packages/jumpstarter-driver-nanokvm/jumpstarter_driver_nanokvm/client.py

Lines changed: 176 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,106 @@ def reboot(self):
298298
"""
299299
self.call("reboot")
300300

301-
def cli(self):
301+
def mount_image(self, file: str = "", cdrom: bool = False):
302+
"""
303+
Mount an image file or unmount if file is empty string
304+
305+
Args:
306+
file: Path to image file on the NanoKVM device, or empty string to unmount
307+
cdrom: Whether to mount as CD-ROM (True) or disk (False)
308+
309+
Note:
310+
Unmounting may fail if image is currently in use. If unmount fails,
311+
you may need to power cycle the connected device first.
312+
313+
Example::
314+
315+
# Mount a disk image
316+
nanokvm.mount_image("/path/to/disk.img", cdrom=False)
317+
318+
# Mount a CD-ROM image
319+
nanokvm.mount_image("/path/to/cdrom.iso", cdrom=True)
320+
321+
# Unmount
322+
nanokvm.mount_image("") or nanokvm.mount_image()
323+
"""
324+
self.call("mount_image", file, cdrom)
325+
326+
def download_image(self, url: str) -> dict:
327+
"""
328+
Start downloading an image from a URL
329+
330+
Args:
331+
url: URL of the image to download
332+
333+
Returns:
334+
Dictionary with download status, file, and percentage
335+
336+
Example::
337+
338+
status = nanokvm.download_image("https://example.com/image.iso")
339+
print(f"Download: {status['status']}, File: {status['file']}, {status['percentage']}%")
340+
"""
341+
return self.call("download_image", url)
342+
343+
def get_mounted_image(self) -> str | None:
344+
"""
345+
Get information about mounted image
346+
347+
Returns:
348+
String with mounted image file path, or None if no image mounted
349+
350+
Example::
351+
352+
file = nanokvm.get_mounted_image()
353+
if file:
354+
print(f"Mounted: {file}")
355+
"""
356+
return self.call("get_mounted_image")
357+
358+
def get_cdrom_status(self) -> bool:
359+
"""
360+
Check if the mounted image is in CD-ROM mode
361+
362+
Returns:
363+
Boolean indicating if CD-ROM mode is active (True=CD-ROM, False=disk)
364+
365+
Example::
366+
367+
if nanokvm.get_cdrom_status():
368+
print("CD-ROM mode is enabled")
369+
"""
370+
return self.call("get_cdrom_status")
371+
372+
def is_image_download_enabled(self) -> bool:
373+
"""
374+
Check if the /data partition allows image downloads
375+
376+
Returns:
377+
Boolean indicating if image downloads are enabled
378+
379+
Example::
380+
381+
if nanokvm.is_image_download_enabled():
382+
print("Image downloads are available")
383+
"""
384+
return self.call("is_image_download_enabled")
385+
386+
def get_image_download_status(self) -> dict:
387+
"""
388+
Get the status of an ongoing image download
389+
390+
Returns:
391+
Dictionary with download status, file, and percentage complete
392+
393+
Example::
394+
395+
status = nanokvm.get_image_download_status()
396+
print(f"Status: {status['status']}, File: {status['file']}, {status['percentage']}%")
397+
"""
398+
return self.call("get_image_download_status")
399+
400+
def cli(self): # noqa: C901
302401
"""Create CLI interface with device management and child commands"""
303402
base = super().cli()
304403

@@ -323,4 +422,80 @@ def reboot():
323422
self.reboot()
324423
click.echo("NanoKVM device is rebooting...")
325424

425+
@base.group()
426+
def image():
427+
"""Image management commands"""
428+
pass
429+
430+
@image.command()
431+
@click.argument("file")
432+
@click.option("--cdrom", is_flag=True, help="Mount as CD-ROM instead of disk")
433+
def mount(file, cdrom):
434+
"""Mount an image file"""
435+
self.mount_image(file, cdrom)
436+
image_type = "CD-ROM" if cdrom else "disk"
437+
click.echo(f"Mounted {file} as {image_type}")
438+
439+
@image.command()
440+
def unmount():
441+
"""Unmount the currently mounted image
442+
443+
Note: Unmount may fail if image is in use by the connected device.
444+
Power cycle the device first if unmount fails.
445+
"""
446+
try:
447+
self.mount_image("")
448+
click.echo("Image unmounted successfully")
449+
except Exception as e:
450+
click.echo(f"Failed to unmount image: {e}", err=True)
451+
click.echo("Note: Image may be in use. Try power cycling the connected device first.", err=True)
452+
raise
453+
454+
@image.command()
455+
@click.argument("url")
456+
def download(url):
457+
"""Download an image from URL"""
458+
status = self.download_image(url)
459+
click.echo(f"Download started: {status['status']}")
460+
if status['file']:
461+
click.echo(f"File: {status['file']}")
462+
if status['percentage']:
463+
click.echo(f"Progress: {status['percentage']}%")
464+
465+
@image.command()
466+
def status():
467+
"""Show mounted image status"""
468+
file = self.get_mounted_image()
469+
if file:
470+
is_cdrom = self.get_cdrom_status()
471+
mode = "CD-ROM" if is_cdrom else "Disk"
472+
click.echo(f"Mounted: {file}")
473+
click.echo(f"Mode: {mode}")
474+
else:
475+
click.echo("No image mounted")
476+
477+
@image.command()
478+
def cdrom_status():
479+
"""Check if mounted image is in CD-ROM mode"""
480+
is_cdrom = self.get_cdrom_status()
481+
mode = "CD-ROM" if is_cdrom else "Disk"
482+
click.echo(f"Current mode: {mode}")
483+
484+
@image.command()
485+
def download_enabled():
486+
"""Check if image downloads are enabled"""
487+
enabled = self.is_image_download_enabled()
488+
status = "enabled" if enabled else "disabled"
489+
click.echo(f"Image downloads: {status}")
490+
491+
@image.command()
492+
def download_status():
493+
"""Get current image download status"""
494+
status = self.get_image_download_status()
495+
click.echo(f"Status: {status['status']}")
496+
if status['file']:
497+
click.echo(f"File: {status['file']}")
498+
if status['percentage']:
499+
click.echo(f"Progress: {status['percentage']}")
500+
326501
return base

0 commit comments

Comments
 (0)