Jump to content

GSoC/2025/StatusReports/kenoi

From KDE Community Wiki

Developing Karton, the KDE Virtual Machine Manager

My aim is to make Karton a native Qt-Quick/Kirigami virtual machine manager, using a libvirt backend. My main goal for GSoC will be to get Karton to a usable state and be well integrated to KDE Plasma. Specifically, developing a native Qt-Quick VM viewer.

The main motivation behind Karton is to provide KDE users with a more Qt-native alternative to GTK-based virtual machine managers, as well as an easy-to-use experience.

karton.png

  • A final look at Karton after the GSoC period.*


konqi.png


Research and Initial Work

<img style="display: block; margin: 0 auto;" src="karqi.png" width="700" />

I had first expressed interest in working on Karton in early Feburary where I made the initial full rewrite ([see MR #4](https://invent.kde.org/sitter/karton/-/merge_requests/4)), using [libvirt](https://libvirt.org/) and a new UI, wrapping [virt-install](https://linux.die.net/man/1/virt-install) and [virt-viewer](https://linux.die.net/man/1/virt-viewer) CLIs. During this time, I had been doing research, writing a [proposal](https://docs.google.com/document/d/13cVp2gISwdFwQyPr8tRzERKnerJkGQpMFffLZmmd9bQ/edit?usp=sharing), and trying out different virtual machine managers like [GNOME Boxes](https://apps.gnome.org/Boxes/), [virtmanager](https://virt-manager.org/), and [UTM](https://mac.getutm.app/).

You can read more about it in my [project introduction blog](https://blogs.kde.org/2025/05/18/gsoc-2025-project-intro-developing-karton-the-kde-virtual-machine-manager/)!

Mentors

Harald Sitter, Nicholas Fella, Tobias Fella

Work report

Week 1:

  • Initial blog
  • Addressing feedback on installer merge request...
  • Improved memory management on domainconfig objects
  • Added DomainConfigData and installer-specific structs for easier instantiation
  • Reworked how classes are exposed to qml by using qmlmodules (instead of at runtime)
  • Worked on osinfoconfig class - wrapped gpointers with uniqueptrs and made patches iso file detection

Week 2:

  • Finalized merging VM Installer MR to master
  • Made small MR patching the xml file directory to be more compatible with other VMMs
  • Continued work on SPICE viewer - rebased and fixed branch
  • Reworked managing client connection to SPICE
  • Debugged receiving image formats to properly render the VM displays

Week 3:

  • Basic input handling (QMouseEvent) achieved - mouse clicks and hover properly sent through SPICE connection
  • Will be on a bit of a break due to midterms :(

Week 4:

  • Implemented keyboard input support, mapping to SPICE (PC XT) scancodes and forwarding controls
  • Implemented mouse scroll support

Week 5:

  • Added handling for active/inactive window focus
  • Wrote second status blog on Qt SPICE Client
  • Reviewed merge request #16 for listing OS variants
  • Implemented proper window resizing and fullscreen functionality

Week 6:

  • Added audio handling, writing to QAudioSink
  • Implemented keypress scancode table from QEMU auto-generated CLI
  • Implemented support for multiple SPICE connections
  • Added mouse drag event handling
  • Finalized SPICE viewer merge request and addressed feedback

Merge Requests

Links to Blogs and other writing

<img src="list.png" width="800" style="display: block; margin: 0 auto;" />

      1. VM Installation

One of my goals for the project was to develop a custom [libvirt domain XML](https://libvirt.org/formatdomain.html) generator using Qt libraries and the [libosinfo](https://libosinfo.org/) GLib API. I started [working on the feature](https://invent.kde.org/sitter/karton/-/merge_requests/8) in advance in April and was able to have it ready for review before the official GSoC coding period.

I created a dialogue menu to accept a VM name, installation media, storage, allocated RAM, and CPUs. `libosinfo` will attempt to identify the ISO file and return a OS short-ID (ex: `fedora40`, `ubuntu24.04`, etc), otherwise users will need to select one from the displayed list.

Through the OS ID, `libosinfo` can provide certain specifications needed in the libvirt domain XML. Karton then fills in the rest, generating a UUID, a MAC address to configure a [virtual network](https://libvirt.org/formatdomain.html#network-interfaces), and sets up display, audio, and storage devices. The XML file is assembled through QDomDocument and passed into a libvirt call that verifies it before adding the VM.

VM information (id, name, state, paths, etc) in Karton is parsed explicitly from the saved libvirt XML file found in the libvirt QEMU folder, `~/.config/libvirt/qemu/{domain_name}.xml`.

All in all, this addition ([see MR #8](https://invent.kde.org/sitter/karton/-/merge_requests/8)) completely removed the `virt-install` dependency although barebones.

<img src="installationdialog.png" width="800" style="display: block; margin: 0 auto;" />
*A screenshot of the VM installation dialog.*

The easy VM installation process of GNOME Boxes had been an inspiration for me and I'd like to improve it in the future by adding a media installer and better error handling later on.

    1. Official Coding Begins!

A few weeks into the official coding period, I had been addressing feedback and polishing [my VM installer merge request](https://invent.kde.org/sitter/karton/-/merge_requests/8). This introduced much cleaner class interface separation in regards to storing individual VM data.

      1. SPICE Client and Viewer

My use of `virt-viewer` previously for interacting with virtual machines was meant as a temporary addition, as it is a separate application and is poorly integrated into Qt/Kirigami and lacks needed customizability.

<img src="virtviewer.png" width="800" style="display: inline-block;" />
*Previously, clicking the `view` button would open a `virtviewer` window.*

As such, the bulk of my time was spent working with [SPICE](https://www.spice-space.org/index.html) directly, using the `spice-client-glib` library, in order to create a custom Qt SPICE client and viewer ([see MR #15](https://invent.kde.org/sitter/karton/-/merge_requests/15)). This needed to manage the state of connection to VM displays and render them to KDE (Kirigami) windows. Other features such as input forwarding, audio receiving also needed to be implemented.

I had configured all Karton-created VMs to be set to [autoport for graphics](https://libvirt.org/formatdomain.html#id75) which dynamically assigns a port at runtime. Consequently, I needed to use a CLI tool, `virsh domdisplay`, to fetch the SPICE URI to establish the initial connection.

The viewer display works through a frame buffer. The approach I took was rendering the pixel array I received to a QImage which could be drawn onto a QQuickItem to be displayed on the window. To know when to update, it listens to the SPICE primary display callback.

You can read more about it in my [Qt SPICE client blog](https://blogs.kde.org/2025/07/10/karton-gsoc-2025-blog-%282-qt-spice-client/). As noted, this approach is quite inefficient as it needs to create a new QImage for every frame. I plan on improving this in the future.

   <img src="noice.png" width="350" style="display: inline-block;" />
   <img src="nnice.png" width="300" style="display: inline-block;" />
  • Screenshots of my struggles getting the display to work properly.*


I had to manage receiving and forwarding Qt input. Sending QMouseEvents, mouse button clicks, were straightforward and can be mapped directly to [SPICE protocol](https://www.spice-space.org/spice-protocol.html) mouse messages when activated. Keystrokes are taken in as QKeyEvents and the received scancodes, in `evdev`, are converted to `PC XT` for SPICE through a [map generated by QEMU](https://gitlab.com/qemu-project/keycodemapdb). Implementing scroll and drag followed similarly.

I also needed manage receiving audio streams from the SPICE playback callback, writing to a QAudioSink. One thing I found nice is how my approach supported multiple SPICE connections quite nicely. For example, opening multiple VMs will create separate audio sources for each so users can modify volume levels accordingly.

Later on, I added display frame resizing when the user resizes the Karton window as well as a fullscreen button. I noticed that doing so still causes resolution to appear quite bad, so proper resizing done through the guest machine will have to be implemented in the future.

{{< video src="https://kenoi.dev/blogs/2025-08-28/view.mp4" >}}

      1. UI

My final major MR was to rework my UI to make better use of screen space [(see MR #25)](https://invent.kde.org/sitter/karton/-/merge_requests/25). I moved the existing VM ListView into a sidebar displaying only name, state, and OS ID. The right side would then have the detailed information of the selected VM. One my inspirations was MacOS UTM's screenshot of the last active frame.

When a user closes the Karton viewer window, the last frame is saved to `$HOME/.local/state/KDE/Karton/previews`. Implementing cool features like these are much easier now that we have our own viewer! I also added some effects for opacity and hover animation to make it look nice.

<img src="manager.png" width="800" style="display: block; margin: 0 auto;" />

Finally, I worked on media disc ejection [(see MR #26)](https://invent.kde.org/sitter/karton/-/merge_requests/26). This uses a [libvirt call](https://libvirt.org/html/libvirt-libvirt-domain.html) to simulate the installation media being removed from the VM, so users can boot into their virtual hard drive after installing.

    1. Demo Usage

As a final test of the project, I decided to configure, configure and use a [Fedora KDE](https://fedoraproject.org/kde/download) VM using Karton. After specifying specs, I installed it to the virtual disk, ejected the installation media, and properly booted into it. Then, I tried playing some games. Overall, it worked pretty well!

{{< video src="https://kenoi.dev/blogs/2025-08-28/install.mp4" >}}

    1. List of MRs
        1. Major Additions:

- [#4 Complete rewrite with libvirt backend, new UI](https://invent.kde.org/sitter/karton/-/merge_requests/4) - [#6 Implement disk path and proper deletion button behavior](https://invent.kde.org/sitter/karton/-/merge_requests/6) - [#8 VM creation through libvirt domain xml format](https://invent.kde.org/sitter/karton/-/merge_requests/8) - [#15 Custom Qt SPICE client and viewer](https://invent.kde.org/sitter/karton/-/merge_requests/15) - [#25 Revamp UI with sidebar and VM preview screencap](https://invent.kde.org/sitter/karton/-/merge_requests/25) - [#26 Implement eject ISO disk button](https://invent.kde.org/sitter/karton/-/merge_requests/26)

        1. Subtle Additions:

- [#10 Update stop VM button icon](https://invent.kde.org/sitter/karton/-/merge_requests/10) - [#14 Store XML path of ~/.config/libvirt/qemu ](https://invent.kde.org/sitter/karton/-/merge_requests/14) - [#17 Fix fullscreen button anchor margin](https://invent.kde.org/sitter/karton/-/merge_requests/17) - [#22 Extract installation dialog into a new class](https://invent.kde.org/sitter/karton/-/merge_requests/22) - [#28 Error notification/prevention for empty installation fields](https://invent.kde.org/sitter/karton/-/merge_requests/28) - [#24 List OS variants through searchable combo box](https://invent.kde.org/sitter/karton/-/merge_requests/24)

    1. Difficulties

My biggest regret was having a study term over this period. I had to really manage my time well, balancing studying, searching for job positions, and contributing. There was a week where I had 2 midterms, 2 interviews, and a final project, and I found myself pulling some late nighters writing code at the school library. Though it's been an exhausting school term, I am still super glad to have been able to contribute to a really cool project and get something work!

I was also new to both C++ and Qt development. Funny enough, I had been taking, and struggling on, my first course in C++ while working on Karton. I also spent a lot of time reading documentation to familiarize myself with a lot of the different APIs (libspice, libvirt, and libosinfo).


   <img src="ram.png" width="350" style="display: inline-block;" />
   <img src="storage.png" width="350" style="display: inline-block;" />
  • Left: Karton freezes my computer because I had too many running VMs.*
  • Right: 434.1 GiB of virtual disks; my reminder to implement disk management.*
    1. What's Next?

There is still so much to do! Currently, I am on vacation and I will be attending Akademy in Berlin in September so I won't be able to work much until then. In the fall, I will be finally off school for a 4 month internship (yay!!). I'm hoping I will have more time to contribute again.

There's still a lot left especially with regards to the viewer.

Here's a bit of an unorganized list:

  • Optimize VM display frame buffer with SPICE `gl-scanout`
  • Improved scaling and text rendering in viewer
  • File transfer and clipboard passthrough with SPICE
  • Full VM snapshotting through libvirt (full duplication)
  • Browse and installation tool for commonly installed ISOs through QEMU
  • Error handling in installation process
  • Configuration and allow modifying of existing VMs in the application
  • Others on the [issue tracker](https://invent.kde.org/sitter/karton/-/issues)
    1. Release?

In its current state, Karton is not feature complete, and not ready for officially packaging and releasing. In addition to the missing features listed before, there have been a lot of new and moving parts throughout this coding period, and I'd like to have the chance to thoroughly test the code to prevent any major issues.

However, I do encourage you to try it out (at your own risk!) [by cloning the repo](https://invent.kde.org/sitter/karton). Let me know what you think and when you find any issues!

In other news, there are some discussions of packaging Karton as a [Flatpak](https://flatpak.org/) eventually and I will be requesting to add it to the [KDE namespace](https://apps.kde.org/) in the coming months, so stay tuned!

    1. Conclusion

Overall, it has been an amazing experience completing GSoC under KDE and I really recommend it for anyone who is looking to contribute to open-source. I'm quite satisfied with what I've been able to accomplish in this short period of time and hoping to continue to working and learning with the community.

Working through MRs has given me a lot of valuable and relevant industry experience going forward. A big thank you to my mentor, [Harald Sitter](https://invent.kde.org/hsitter), who has been reviewing and providing feedback along the way!

As mentioned earlier, Karton still definitely has a lot to work on and I plan continuing my work after GSoC as well. If you'd like to read more about my work on the project in the future, please check out [my personal blog](https://kenoi.dev/) and the development matrix, [karton:kde.org](https://matrix.to/#/#karton:kde.org).

Thanks for reading!

<img src="qtd.png" width="500" />


      1. Socials


Website: https://kenoi.dev/

Mastodon: https://mastodon.social/@kenoi

GitLab: https://invent.kde.org/kenoi

GitHub: https://github.com/kenoi1

Matrix: @kenoi:matrix.org

Discord: kenyoy