Skip to content

Sunday, 31 August 2025

Window Activation, what’s that?

In short: for some actions you want to activate your application window. How to do that differs from platform to platform.

Some weeks ago there was a post by Kai about window activation on Wayland.

It explains nicely how the XDG Activation protocol works and how the different parts of the KDE & Qt stack implement it to correctly allow to activate the right windows in your KDE Plasma Wayland session.

What’s the issue?

For the most parts this works now flawlessly, but there is some corner case not handled that well at the moment: stuff launched by our terminal emulator Konsole.

This is no large issue for many users, but I rely in my usual workflow with Kate a lot on just being able to do

❯ kate mycoolfile.cpp

in my Konsole window and if Kate is already running to have the existing window activated.

That worked just fine on X11, but on Wayland the safeguards to avoid misuse of window activation stopped that. There I just get some flashing of the Kate icon in the task switcher.

After some years of ignoring that issues myself and hoping a solution would just materialize, I now gave that some try to fix it myself.

The hack (solution)

In the most cases it is easy to get the application that launches stuff to export a new XDG_ACTIVATION_TOKEN environment variable for the new process and all stuff just works.

Unfortunately that is not that easy for a terminal emulator.

Naturally Konsole could create and export one before starting the shell, but that is an one-time use token. In the best case the first launched Kate or other application could use it once.

The current solution is to allow applications to query a XDG_ACTIVATION_TOKEN via DBus from Konsole, if they can prove they got started inside the shell session by passing a KONSOLE_DBUS_ACTIVATION_COOKIE that got exported by Konsole to the shell.

This leads to the following client code in Kate/KWrite and Konsole itself.

    // on wayland: init token if we are launched by Konsole and have none
    if (KWindowSystem::isPlatformWayland()
        && qEnvironmentVariable("XDG_ACTIVATION_TOKEN").isEmpty()
        && QDBusConnection::sessionBus().interface()) {
        // can we ask Konsole for a token?
        const auto konsoleService = qEnvironmentVariable("KONSOLE_DBUS_SERVICE");
        const auto konsoleSession = qEnvironmentVariable("KONSOLE_DBUS_SESSION");
        const auto konsoleActivationCookie
            = qEnvironmentVariable("KONSOLE_DBUS_ACTIVATION_COOKIE");
        if (!konsoleService.isEmpty() && !konsoleSession.isEmpty()
            && !konsoleActivationCookie.isEmpty()) {
            // we ask the current shell session
            QDBusMessage m =
                QDBusMessage::createMethodCall(konsoleService,
                    konsoleSession,
                    QStringLiteral("org.kde.konsole.Session"),
                    QStringLiteral("activationToken"));

            // use the cookie from the environment
            m.setArguments({konsoleActivationCookie});

            // get the token, if possible and export it to environment for later use
            const auto tokenAnswer = QDBusConnection::sessionBus().call(m);
            if (tokenAnswer.type() == QDBusMessage::ReplyMessage
                && !tokenAnswer.arguments().isEmpty()) {
                const auto token = tokenAnswer.arguments().first().toString();
                if (!token.isEmpty()) {
                    qputenv("XDG_ACTIVATION_TOKEN", token.toUtf8());
                }
            }
        }
    }

The KONSOLE_DBUS_SERVICE and KONSOLE_DBUS_SESSION environment variables did already exist before, the only new one is the secret KONSOLE_DBUS_ACTIVATION_COOKIE that is filled by Konsole with some random string per shell session.

The org.kde.konsole.Session has now a new activationToken method to retrieve a token. That method will only work if the right cookie is passed and if Konsole itself is the current active window (else it will not be able to request a token internally).

As it easily visible alone from the naming, that is some Konsole only interface and one needs own code in applications that want to make use of it.

Therefore this is more of a hack than a proper solution.

If it inspires others to come up with something generic, that would be awesome!

I will happily help to implement that in Kate and Co., but until that happens, at least my workflow is back working in my Wayland session.

Feedback

You can provide feedback on the matching Lemmy and Reddit posts.

Hello again! I'm Ajay Chauhan, and this update continues my Google Summer of Code 2025 journey with Kdenlive. Over the past few months, I've been working on transforming Kdenlive's marker system from simple point markers to a range-based markers. Let me take you through the progress we've made since my last update.

From Backend to Frontend:

Building the User Interface

The real magic happened when we started building the user interface. I began with the Marker Dialog - the window where users create and edit markers. This was a significant challenge because we needed to maintain the simplicity of point markers while adding the complexity of range functionality.

I added three new UI elements:

  • A checkbox to toggle between point and range markers
  • An "End Time" field for setting the marker's end position
  • A "Duration" field that automatically calculates and displays the time span

The trickiest part was keeping these fields synchronized. When a user changes the start time, the duration updates automatically. When they modify the duration, the end time adjusts accordingly. It's like having three interconnected gears that always move together.

🔗 Commit: feat(range-marker): implement range marker functionality in MarkerDialog

Visual Magic in the Monitor

Next came the Monitor Ruler - the horizontal timeline you see above the video preview. This is where range markers work visually with the video preview.

I implemented a visual system:

  • Range Span: A semi-transparent colored rectangle that shows the marker's duration
  • Start and End Lines: Vertical lines marking the beginning and end of each range
  • Color Consistency: Each marker type gets its own color, with range markers using that color for the entire span

The core of this visualization is the rangeSpan rectangle:

Rectangle {
    id: rangeSpan
    visible: guideRoot.isRangeMarker
    x: (model.frame * root.timeScale) - ruler.rulerZoomOffset
    width: Math.max(1, guideRoot.markerDuration * root.timeScale)
    height: parent.height
    color: Qt.rgba(model.color.r, model.color.g, model.color.b, 0.5)
}

What this does:

  • visible: guideRoot.isRangeMarker - Only shows for range markers (not point markers)
  • width: Math.max(1, guideRoot.markerDuration * root.timeScale) - Width represents duration, with a minimum of 1 pixel
  • color: Qt.rgba(...) - Uses the marker's category color with 50% transparency

But the real challenge was making these markers interactive. Users can now drag the left or right edges of a range marker to resize it in real-time. The visual feedback is immediate - you see the marker grow or shrink as you drag, making it incredibly intuitive.

🔗 Commit: feat(monitor-ruler): Display the range marker color span in the Clip Monitor

Timeline Integration

The Timeline Ruler was the next frontier. This is the main timeline where users spend most of their editing time, so the range markers needed to be even more sophisticated here.

I added several visual features similar to the monitor ruler.

The timeline implementation also includes the same drag-to-resize functionality, but with additional constraints to prevent markers from extending beyond clip boundaries.

🔗 Commit: feat(markers): drag-to-resize range markers in monitor and timeline

The drag-to-resize

The drag-to-resize functionality was perhaps the most technically challenging feature. I had to implement:

  • Left and Right Handle Resizing: Dragging the left and right edges changes both the start position and duration
  • Live Preview: Visual feedback during resize operations
  • Constraint Handling: Preventing invalid durations (minimum 1 frame)
  • Binding Management: Restoring Qt's automatic updates after resize completion

The resize handles only appear when range markers are wide enough, and they provide visual feedback through color changes and opacity adjustments. Here's how the left and right resize handle works:

Rectangle {
    id: leftResizeHandle
    visible: guideRoot.isRangeMarker && rangeSpan.width > 10
    width: 4
    height: parent.height
    x: rangeSpan.x
    color: Qt.darker(model.color, 1.3)
    opacity: leftResizeArea.containsMouse || leftResizeArea.isResizing ? 0.8 : 0.5

    MouseArea {
        id: leftResizeArea
        anchors.fill: parent
        anchors.margins: -2  // Extends clickable area
        cursorShape: Qt.SizeHorCursor
        preventStealing: true

        onPositionChanged: {
            if (isResizing) {
                var globalCurrentX = mapToGlobal(Qt.point(mouseX, 0)).x
                var realDeltaX = globalCurrentX - globalStartX
                var deltaFrames = Math.round(realDeltaX / root.timeScale)
                var newStartPosition = Math.max(0, startPosition + deltaFrames)

                // Live preview updates
                rangeSpan.x = (newStartPosition * root.timeScale) - ruler.rulerZoomOffset
                rangeSpan.width = Math.max(1, newDuration * root.timeScale)
            }
        }
    }
}

Key implementation details:

  • anchors.margins: -2 - Extends the clickable area beyond the visible handle
  • preventStealing: true - Prevents other mouse areas from interfering
  • Global coordinate tracking ensures accurate resize calculations across zoom levels
  • Live preview updates provide immediate visual feedback

🔗 Commit: feat(monitor-ruler): enable right-click capture for resizing markers

Zone-to-Marker

One of the other features I implemented was the Zone-to-Marker Conversion system. This feature allows users to define a time zone in the monitor and instantly create a range marker from it, bridging the gap between Kdenlive's existing zone functionality and the new range marker system.

Before this feature, users would have to manually create range markers.

This was time-consuming and error-prone, especially when working with precise time ranges that were already defined as zones.

How It Works

The zone-to-marker system works in two ways:

Method 1: Context Menu Integration Users can right-click on the monitor ruler and select "Create Range Marker from Zone" from the context menu. This instantly creates a range marker spanning the currently defined zone.

Method 2: Quick Action A dedicated action that can be triggered from the main window, allowing users to quickly convert zones to markers without navigating through menus.

1. Monitor Proxy Enhancement I added a new method to the MonitorProxy class that handles the zone-to-marker conversion:

bool MonitorProxy::createRangeMarkerFromZone(const QString &comment, int type)
{
    // Validate zone boundaries
    if (m_zoneIn <= 0 || m_zoneOut <= 0 || m_zoneIn >= m_zoneOut) {
        return false;
    }

    std::shared_ptr<MarkerListModel> markerModel;

    // Determine which marker model to use based on monitor type
    if (q->m_id == int(Kdenlive::ClipMonitor)) {
        auto activeClip = pCore->monitorManager()->clipMonitor()->activeClipId();
        if (!activeClip.isEmpty()) {
            auto clip = pCore->bin()->getBinClip(activeClip);
            if (clip) {
                markerModel = clip->getMarkerModel();
            }
        }
    } else {
        // For project monitor, use the timeline guide model
        if (pCore->currentDoc()) {
            markerModel = pCore->currentDoc()->getGuideModel(pCore->currentTimelineId());
        }
    }

    if (!markerModel) {
        return false;
    }

    // Convert zone to range marker
    GenTime startPos(m_zoneIn, pCore->getCurrentFps());
    GenTime duration(m_zoneOut - m_zoneIn, pCore->getCurrentFps());
    QString markerComment = comment.isEmpty() ? i18n("Zone marker") : comment;

    // Use default marker type if none specified
    if (type == -1) {
        type = KdenliveSettings::default_marker_type();
    }

    bool success = markerModel->addRangeMarker(startPos, duration, markerComment, type);
    return success;
}

User Experience Features

1. Smart Validation The system validates zone boundaries before creating markers:

  • Ensures zone start is before zone end
  • Prevents creation of zero-duration zones
  • Handles edge cases gracefully

2. Automatic Naming If no comment is provided, the system automatically generates a descriptive name like "Zone marker" or uses the existing zone name if available.

3. Feedback System Users receive immediate feedback through status messages:

  • Success confirmation when markers are created
  • Error messages for invalid operations
  • Warning messages for missing zones

Features

Timeline Controller Integration: Added methods like resizeGuide and suggestSnapPoint to make range markers work seamlessly with Kdenlive's existing timeline operations.

The backend integration happens through the resizeMarker method in the monitor proxy:

void MonitorProxy::resizeMarker(int position, int duration, bool isStart, int newPosition)
{
    std::shared_ptr<MarkerListModel> markerModel;

    // Determine appropriate marker model based on monitor type
    if (q->m_id == int(Kdenlive::ClipMonitor)) {
        auto activeClip = pCore->monitorManager()->clipMonitor()->activeClipId();
        if (!activeClip.isEmpty()) {
            auto clip = pCore->bin()->getBinClip(activeClip);
            if (clip) {
                markerModel = clip->getMarkerModel();
            }
        }
    }

    if (markerModel) {
        GenTime pos(position, pCore->getCurrentFps());
        bool exists;
        CommentedTime marker = markerModel->getMarker(pos, &exists);

        if (exists && marker.hasRange()) {
            GenTime newDuration(duration, pCore->getCurrentFps());
            // Apply constraints and update the marker
            if (newDuration < GenTime(1, pCore->getCurrentFps())) {
                newDuration = GenTime(1, pCore->getCurrentFps());
            }
            markerModel->editMarker(pos, newStartTime, marker.comment(),
                                  marker.markerType(), newDuration);
        }
    }
}

🔗 Commit: feat: add functionality to create range markers from defined zones

What This Means for Kdenlive Users

Before Range Markers

Users could only place markers at specific points in time. To mark a section, they'd need multiple point markers and remember which ones belonged together.

After Range Markers

Users can now:

  • Mark Complete Sections: Create a single marker that spans an entire intro, chapter, or highlight
  • Visual Organization: See at a glance which parts of their project are marked and how long each section is
  • Efficient Editing: Resize markers to adjust section boundaries without recreating them
  • Better Collaboration: Share projects with clear, visual section markers

Final Thoughts

This GSoC project has been an incredible journey. From the initial concept of extending Kdenlive's marker system to the final implementations of a fully featured range marker interface, every step has been a learning experience. I still have some things to improve in the Merge Request, but I'm happy with the progress I've made.

I'm grateful to my mentor Jean-Baptiste Mardelle for his guidance throughout this project, and to the entire Kdenlive community for their support and feedback in the Merge Request.

As I move forward in my studies and career, I'll always remember this summer spent improving Kdenlive's marker system. The skills I've developed, the challenges I've overcome, and the community I've been part of will continue to influence my work for years to come.


Saturday, 30 August 2025

Hello again!

This is the second part of my GSoC journey with the KDE community.
In my previous blog, I introduced my project “Modernize Account Management with QML” and shared how I worked on building a shared infrastructure for Akonadi and ported the Knut configuration dialog to QML.

Now, I’m excited to share the final part of my work and wrap up this amazing journey.


Work Accomplished

Since my last update, I successfully ported the configuration dialogs for singlefileresource-based resources to QML, marking a significant milestone in modernizing account management.

The new architecture leverages the shared infrastructure I built earlier (QuickAgentConfigurationBase) and consists of two main parts:

  1. Common QML Component (SingleFileConfig.qml):

    • A reusable, Kirigami-based form component.
    • Handles universal settings: file path selection, display name, read-only mode, monitoring, and periodic updates.
    • Eliminates code duplication and ensures a consistent look and feel.
  2. Resource-Specific QML Wrappers:

    • Each resource (Ical, Vcard, Mbox) now has its own Main.qml.
    • Uses a TabBar layout to organize common settings from SingleFileConfig and resource-specific tabs (like compaction for Mbox or activities configuration).

Resources Ported

  • Ical Resource:

    • Migrated calendar file (.ics) configuration.
    • Now provides a clean, two-tab interface (File and Activities) built with Kirigami FormCard components.
    • Offers a more intuitive user experience.
  • Vcard Resource:

    • Migrated address book (.vcf) configuration.
    • Confirmed the reusability of the SingleFileConfig component.
  • Mbox Resource:

    • The most complex port.
    • Included unique tabs for Compact Frequency and Lock Method.
    • Old QWidget .ui files and C++ classes replaced with pure QML components (CompactPage.qml, LockMethodPage.qml).
    • Integrated directly into the new configuration base class.

Lessons Learned

  • Navigating Large Codebases: Learned how to work in KDE’s modular ecosystem, tracing dependencies and understanding project structure.
  • Debugging & Documentation: Improved my debugging skills across QML/C++ and writing clear documentation for future developers.
  • Mentorship & Feedback: Mentor guidance helped refine my coding style, problem-solving approach, and overall contributions.

Looking Ahead

While the main scope of my project is complete, there’s still plenty of room to grow.
I plan to continue contributing to KDE by porting more configuration dialogs to QML as time permits.

I’m deeply grateful to my mentors — Carl Schwan, Claudio Cambra, and Aakarsh MJ — and every KDE developer for their constant guidance, patience, and support throughout this journey. Thank you so much!

Hello everyone, this is going to be the final blog post of my GSoC 2025 project. In this post, I will summarize the progress made during the project and discuss the future plans for expanding OSS-Fuzz integration across KDE libraries.

Quick Recap Of Progress

So far, I had integrated several KDE libraries into OSS-Fuzz, including KMime, KIO-Extras/thumbnail, and KFileMetaData (submitted for integration).

I have also moved existing projects from OSS-Fuzz repository to KDE repositories.

Progress After Midterm

After midterm, in the first half I focused on integrating new thumbnailers into OSS-Fuzz. I had already integrated KIO-Extras/thumbnail, and I continued with KDEGraphics-Thumbnailers, KDESDK-Thumbnailers, and FFMpeg-Thumbs.

After that, I mostly worked on improving the existing integration, i.e, testing the fuzzers and fixing any issues, moving to CMake based setup instead of manual compilation of the fuzzers and adding documentation for local testing of the fuzzers.

The CMake setup allowed for easier maintenance however, it wasn’t as simple as it may seem. Since OSS-Fuzz recommends using static builds, many of the libraries didn’t link to their transitive dependencies correctly for static builds. This required changes to those libraries (.pc files, .cmake files, etc) for proper static linking.

The existing setup also lacked documentation for local testing of the fuzzers. I have added documentation for almost all of the fuzzers. This will be helpful for developers to integrate new fuzzers (such as new thumbnailers or KFileMetaData extractors).

Future Plans

With the initial setup of thumbnailers and KFileMetaData, it is easy to integrate new thumbnailers and KFileMetaData extractors. Currently there are a few more thumbnailers that could be integrated into OSS-Fuzz, I plan to work on integrating them soon as well, the list is here:


Thank You

I would like to thank my mentor, Albert Astals Cid, and the KDE community for their guidance throughout this project. Their feedback was helpful in successfully expanding OSS-Fuzz integration across KDE libraries.

Friday, 29 August 2025

When travelling, I tend to rely on public Wi-Fi hotspots a lot, for example on trains, while waiting at the station, in cafe’s and so on.

Accepting the same terms and conditions every time gets annoying pretty quickly, so a few years ago I decided to automate this. The project that came out of that is freewifid.

It continously scans for Wi-Fi networks it knows, and sends you a notification when it found one it can automatically connect to. You can then allow it to connect to that network automatically in the future.

A freewifid notification asking whether to connect to a known network

Adding support for new captive portals

Adding support for a new kind of captive portal is pretty easy. You just need to implement a small rust trait that includes a function that sends the specific request for the captive portal. Often this is very simple and looks like this:

pub struct LtgLinkProvider {}

impl LtgLinkProvider {
    pub fn new() -> LtgLinkProvider { LtgLinkProvider {} }
}

impl CaptivePortal for LtgLinkProvider {
    fn can_handle(&self, ssid: &str) -> bool {
        ["Link WIFI"].contains(&ssid)
    }

    fn login(&self, http_client: &ureq::Agent) -> anyhow::Result<()> {
        // Store any cookies the landing page might send
        common::follow_automatic_redirect(http_client)?;

        http_client
            .post("http://192.168.1.100:8880/guest/s/default/login")
            .send_form([
                ("checkbox", "on"),
                ("landing_url", crate::GENERIC_CHECK_URL),
                ("accept", "PRISIJUNGTI"),
            ])?;

        Ok(())
    }
}

For finding out what needs to be sent, you can use your favoute browser’s inspection tools.

For testing, Plasma’s feature for assigning a random MAC-address comes very handy.

Integration with Plasma

It could be interesting to write a KCM for freewifid, so you can graphically remove networks again. Support for ignoring public networks in the presence of a given SSID is also already implemented, but currently needs to be enabled by editing the config file. Writing a KCM is not high on my list of priorities right now, but if this sounds like something you’d like to do, I’d happily help with with the freewifid interfacing parts.

Project

The project is hosted on Codeberg. I’ll happily accept merge requests for additional captive portals there.

There are some prebuilt release binaries, but I’m not too certain they’ll work on every distribution. With a rust compiler installed, the project is very simple to build (cargo build). A systemd unit is provided in the repository, which you can use to run freewifid as a user unit.

Freewifid also supports running as a system service non-interactively for use in embedded projects.

Thursday, 28 August 2025

After I took a longer break from KDE development, I’ve been back in action for a few months now. It’s really nice to be back among friends, hacking on what I like most: Plasma. My focus has been on Plasma Mobile with some work naturally bleeding over into other areas.

Plasma on more Devices

I’d like to share some bits and pieces that I’ve worked on in the past months. Most of my efforts have revolved around making Plasma Mobile suitable for a wider range of devices and use-cases. The purpose of this work is that I want to make Plasma Mobile a more viable base for all kinds of products, not just mobile phones. We have a really mature software stack and great tools and applications which make it relatively easy for companies to create amazing products without having to hire large teams and many years to get the product ready for their market. This is I think a very interesting and worthwhile niche for Plasma to get into and I’m sure that Valve is not the only company that understands this.

Convergence Improvements

Convergence, or rather being able to support and switch between formfactors and usage patterns has always been a pet-peeve of mine and still is.
One area was improving using the available screen real estate use landscape displays (Plasma Mobile has quite naturally been rather “portrait-focused”, though a few smaller patches go a long way.)

Configurable number of columns in the Quicksettings drawer

I also improve usability with different pixel densities in the mobile shell by making the size of the top panel configurable. Also, when plugging in a second monitor, Plasma Mobile now switches from “all apps are maximized” to normal window management. (I’m currently working on KWin supporting more fine-grained window management. Currently, we just maximize all windows which has problems especially with modal dialogs.)

One changeset I worked on earlier this year makes it possible to ship multiple user interfaces for settings modules (“kcms”). An example is the “remote desktop” kcm which now shows a mobile-focused UI in Plasma Mobile. What happens here is that we load a main_phone.qml file in Plasma Mobile (where “phone” is picked from a list of form factors set in the environment of the session, so basically the “main” QML file gets picked based on the device. This mechanism allows us to share components quite easily, reducing the delta between different device UIs.

Mobile and Desktop RDP settings

This actually builds on top of work that I’ve done ten years ago which added support for form factors to our plugin metadata system.
I’ve also made the “Display & Monitor” kcm usable on mobile, this is a pretty important thing to have working when you want to be able to plug in an external monitor into your device. I have a mobile version of the keyboard KCM in the pipeline, too, but this will need a bit more work before it’s ready for prime-time.

More Features

There’s a new page in the mobile Wi-fi settings module, showing connection details and tranfer speeds. The code for this was amazingly simple since I could lift most of the functionality from the desktop panel widget. A shared code-base across devices really speeds up development.

Connection details for the mobile wifi settings

Adding useful features here and there, such as having the list of available bluetooth devices now filtered by default and only showing devices which actually make sense to pair (with an option to “Show all devices” in good Plasma manner). This feature isn’t mobile-specific, so desktop and laptop users will benefit.

Welcome to Okular Mobile

Not all my work goes into infrastructural and “shell” bits. The mobile okular version has now kind of caught up with the desktop version since it got a nice welcome screen when opened. This allows the user to easily open a document either from the “Documents” directory on disk (this is actually configurable) or one of the recent files viewed.

Okular Mobile Welcome Screen

Going to Akademy ’25

After having missed our yearly world conference for a number of years, this year I will be at Akademy again. I’m really looking forward to seeing everybody in person again!

I’m going to Akademy!

See you in Berlin!

Hello again everyone!

I’m Derek Lin also known as kenoi, a second-year Math student at the University of Waterloo.

Through Google Summer of Code 2025 (GSoC), mentored by Harald Sitter, Tobias Fella, and Nicolas Fella, I have been developing Karton, a virtual machine manager for KDE.

As the program wraps up, I thought it would be a good idea to put together what I’ve been able to accomplish as well as my plans going forward.

A final look at Karton after the GSoC period.

Research and Initial Work

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.

I had first expressed interest in working on Karton in early Feburary where I made the initial full rewrite (see MR #4), using libvirt and a new UI, wrapping virt-install and virt-viewer CLIs. During this time, I had been doing research, writing a proposal, and trying out different virtual machine managers like GNOME Boxes, virtmanager, and UTM.

You can read more about it in my project introduction blog!

A screenshot of my rewrite in March 8, 2025.

VM Installation

One of my goals for the project was to develop a custom libvirt domain XML generator using Qt libraries and the libosinfo GLib API. I started working on the feature 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, 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) completely removed the virt-install dependency although barebones.

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.

Official Coding Begins!

A few weeks into the official coding period, I had been addressing feedback and polishing my VM installer merge request. This introduced much cleaner class interface separation in regards to storing individual VM data.

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.

Previously, clicking the view button would open a virtviewer window.

As such, the bulk of my time was spent working with SPICE directly, using the spice-client-glib library, in order to create a custom Qt SPICE client and viewer (see MR #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 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. 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.

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 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. 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.

Now, we can watch Pepper and Carrot somewhat! (no hardware accelleration yet)

UI

My final major MR was to rework my UI to make better use of screen space (see MR #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.

Finally, I worked on media disc ejection (see MR #26). This uses a libvirt call to simulate the installation media being removed from the VM, so users can boot into their virtual hard drive after installing.

Demo Usage

As a final test of the project, I decided to create, configure and use a Fedora KDE VM using Karton. After setting specifications, 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!

List of MRs

Major Additions:

Subtle Additions:

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).

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.

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

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. 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 eventually and I will be requesting to add it to the KDE namespace in the coming months, so stay tuned!

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, 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 and the development matrix, karton:kde.org.

Thanks for reading!

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

Catching Up

These last few weeks have been pretty hectic due to me moving countries and such, so I have not had the time to write a blog post detailing my weekly progress, because of this I have decided to compress it all into a singular blog post talking about all the changes I have been working on and what I plan on doing in the future.


The NewMailNotifier Agent

In the last blog post I wrote I talked about the progress that had been made in the newmailnotifier agent, and that in the following weeks I would finish implementing the changes and testing its funcionality. Well, it ended up taking quite a bit longer as I found that several other files had to also be moved to KMail from KDE-PIM Runtime, and these ones were being used in the runtime repo. The files I have found so far and that I have been looking into are:

  • newmailnotificationhistorybrowsertext.cpp
  • newmailnotificationhistorybrowsertext.h
  • newmailnotificationhistorybrowsertextwidget.cpp
  • newmailnotificationhistorybrowsertextwidget.h
  • newmailnotificationhistorydialog.cpp
  • newmailnotificationhistorydialog.h
  • newmailnotificationhistorywidget.cpp
  • newmailnotificationhistorywidget.h
  • newmailnotifieropenfolderjob.cpp
  • newmailnotifieropenfolderjob.h
  • newmailnotifiershowmessagejob.cpp
  • newmailnotifiershowmessagejob.h

The Troublesome Migration Agent

The MR for the singleshot capability in the Akonadi repo was given the green light and just recently got merged. On the other hand, the MR with the changes for the agent received feedback and several improvements were requested.

Most importantly, Carl brought to my attention how recent MR’s by Nicolas Fella removed the job tracker from the migration agent, thus making it unnecessary to add it as a temporary folder. Both the requested changes and the removal of the folder have been carried out, while doing so I even realized that in my singleshot MR I was missing the addition of the new finished()signal in the agentbase header file, which I have now also added.

After doing this though, I once again focused on the problem that persisted, the singleshot capability not working properly. The migration agent would initialize without issue when running the Akonadi server but would then not shut down after completing its tasks. I knew that the isPluginOpen() method worked in sending the finished signal, as when I opened and closed the plugin the agent would shut down correctly.

With the help of my mentor Claudio, we found that the migrations were in fact not even running, the agent would start but the jobs would fail to run, because of this the logic implemented to signal the finilization of a job never had the chance to run, and thus isPluginOpen()remained untouched.

Furthermore, the way I had designed the plugin letting the agent know that it was open had proven to be insufficient, as the migrations (once we get them to run as intended) would emit the jobFinished() signal after concluding, thus triggering the isPluginOpen() method with the default value of false and shutting down the agent, even if the plugin was still open.

The times the singleshot capability did work (when opening and closing the plugin), we also found that the status would show as “Broken” and the statusMessage as “Unable to start”, which may need changing, but most troubling was that the opening of the plugin would not restart the agent, therefore only showing an empty config window. I need to find a way to either restart from the agent itself or notify Akonadi so that it restarts it when the plugin runs.


Current Status and What’s Next

The GSOC concludes next week and these last few weeks have not seen any MR requests from my part, so my plan is to continue with the refactoring beyond the end of the programme, working on completing the NewMailNotifier and Migration agents, as well as dealing with a few of the agents in KMail, namely MailFilter, MailMerge and the UnifiedMailBox.

As of now, the identified issues to solve regarding the Migration agent are:

  • The agent not knowing if the plugin is open or closed when emitting the finished() signal.
  • The migrations not running.
  • The status and statusMessage showing as “Broken” and “Unable to start”, respectively.
  • The agent not being able to restart itself.

In the case of the NewMailNotifier:

  • Complete the transfer of the UI related logic to KMail
  • Test the D-Bus connection and the modified slotShowNotificationHistory()

While there’s still work ahead, I feel that these weeks have been invaluable in terms of learning, debugging, and understanding the bigger picture of how the different Akonadi agents fit together. The experience has been both challenging and rewarding, and I’m looking forward to tackling the remaining issues with a clearer path forward.

Although GSoC is officially ending, this is just a milestone rather than a finish line, and I’m excited to continue contributing to Merkuro and the KDE ecosystem as a whole.

Monday, 25 August 2025

Intro

In my final week of GSoC with KDE's Krita this summer, I am excited to share this week's progress and reflect on my journey so far. From the initial setup to building the Selection Action Bar, this project has been a meaningful learning experience and a stepping stone toward connecting with Krita's community and open source development.

Final Report

Progress

This week I finalized the Selection Action Bar with my mentor Emmet and made adjustments based on my merge request feedback.

Some key areas of feedback and fixes included:

  • Localization of user-facing strings
  • Removing unused parameters
  • Refactoring naming conventions and standardized styling

These improvements taught me that writing good code is not just about features, but also about clarity, consistency, and collaboration.

Alongside updating my feature merge request, I also worked on documentation to explain how the Selection Action Bar works and how users can use it.

Reflection

Looking back over the past 12 weeks, I realize how much this project has shaped both my technical and personal growth as a developer.

Technical Growth When I started, navigating Krita's large C++/Qt codebase felt overwhelming. Through persistence, code reviews, and mentorship, I've grown confident in reading unfamiliar code, handling ambiguity, and contributing in a way that fits the standards of a large open source project. Following Krita's style guidelines showed me how important naming conventions and standardized code styling are for long-term maintainability.

Personal Growth One of the most important lessons I learned is that open source development isn't about rushing to get the next feature in. It's about patience, clarity, and iteration. Code reviews taught me to embrace feedback, ask better questions, and view them as opportunities for growth rather than blockers.

Community Lessons The most valuable part of this experience was connecting with the Krita and KDE community. I experienced first-hand how collaborative and thoughtful the process of open source development is. Every suggestion, from small style tweaks to broader design decisions, carried the goal of improving the project for everyone. That sense of shared ownership and responsibility is something I want to carry with me in all my future contributions.

Conclusion

These final weeks have been very rewarding. I have grown from starting out by simply reading Krita's large codebase to implementing a feature that enhances users' workflow.

While this marks the end of GSoC for me, it is not the end of my open source journey. My plan moving forward is to:

  • Continue refining the Selection Action Bar based on user feedback
  • Add customization options to the Selection Action Bar
  • Stay involved through ownership of feature creation, bug fixes, community participation, and feature proposals with the Krita and KDE community

Finally, I would like to thank my mentor Emmet, the Krita Developers Dmitry, Halla, Tiar, Wolthera, everyone I interacted with in Krita Chat, and the Krita community for their guidance, patience, and encouragement throughout this project.

I also want to thank Google Summer of Code for making this journey possible and giving me the chance to grow as a developer while contributing to open source.

Contact

To anyone reading this, please feel free to reach out to me. I'm always open to suggestions and thoughts on how to improve as a developer and as a person.
Email: ross.erosales@gmail.com
Matrix: @rossr:matrix.org

Sunday, 24 August 2025


Implementation of Python virtual environment runtime switching

A long‑running backend that evaluates Python code must solve one problem well: switching the active interpreter or virtual environment at runtime without restarting the host process. A reliable solution depends on five pillars: unambiguous input semantics, reproducible version discovery, version‑aware initialization, disciplined management of process environment and sys.path, and transactional switching that can roll back safely on failure. 


The switching workflow begins with a single resolver that accepts either an interpreter executable path or a virtual environment directory. If the input is a file whose basename looks like a Python executable, the resolver treats it as such, and when the path sits under bin or Scripts it walks one directory up to infer the venv root. If the input is a directory, the resolver confirms a venv by checking for pyvenv.cfg or conda‑meta. Inputs that do not meet either criterion are interpreted as requests to use the system Python. One subtle but important detail is to avoid canonicalizing paths during this phase. Symlinked venvs frequently point into system trees; resolving them prematurely would collapse a virtual environment back into “system Python,” undermining the caller’s intent.

Pic 1. Project structure created by venv/virtualenv
Pic 2. Project structure in {virtual_env_path}/bin
Pic 3. Project structure created by conda


Once a target has been identified, the backend determines the interpreter’s major.minor version and applies a session‑level version policy. Virtual environments often publish their version and preferred executable in pyvenv.cfg; the backend reads version, executable and base‑executable if present, falling back to executing the interpreter with a small snippet to print its major and minor components when necessary. For system Python, a small set of common candidates are probed until one responds. At first login, the backend records the initialized major.minor pair and considers subsequent switches compatible only if they match that normalized value. This deliberately conservative choice prevents ABI mismatches inside a single process.

Pic 5.  Content in pyvenv.cfg

Initialization deliberately follows two distinct paths because Python’s embedding APIs changed significantly in 3.8. For older runtimes, the legacy sequence sets the program name and Python home using Py_SetProgramName and Py_SetPythonHome and then calls Py_Initialize. To keep the embedded interpreter’s view of the world coherent, the backend then runs a short configuration script that clears and rebuilds sys.path, sets sys.prefix and sys.exec_prefix, and establishes VIRTUAL_ENV in os.environ. This legacy path also relies on process‑level environment manipulation, which is described below. For modern runtimes, the backend uses the PyConfig API. It constructs an isolated configuration, sets program_name, home, executable and base_executable explicitly, marks module_search_paths_set, and appends each desired search path through PyWideStringList_Append before calling Py_InitializeFromConfig. This approach minimizes dependence on ambient process environment and makes the search space explicit and predictable. It is worth emphasizing that even when switching to the system interpreter on Py≥3.8, module search paths should be set explicitly rather than relying on implicit heuristics.


The legacy initialization path leans on controlled modification of the host process environment. Before entering a venv, the backend saves the current PATH and PYTHONHOME, prepends the venv’s bin or Scripts directory to PATH, unsets PYTHONHOME and clears PYTHONPATH, and sets VIRTUAL_ENV. On restore, PATH and PYTHONHOME are put back, VIRTUAL_ENV and PYTHONPATH are cleared, and a guard bit records that the environment is no longer modified. A frequent source of instability in ad‑hoc implementations is PATH inflation during rapid switching. The fix is straightforward: always rebuild PATH from the original value captured before the first switch rather than stacking new prefixes on top of already mutated values.


Search path construction is handled in two places. On the C++ side, we can expand the venv’s library layout into a concrete list of directories—lib/pythonX.Y/site‑packages, lib/pythonX.Y, and lib64 variants—and, if desired, appends a fallback set of system paths. On the Python side, a short configuration fragment clears sys.path and appends the new list in order, then sets sys.prefix and sys.exec_prefix to the venv root and publishes VIRTUAL_ENV in the environment. Projects that require strict isolation can omit the system fallback entirely or tie the decision to pyvenv.cfg’s include‑system‑site‑packages.


Switching itself is transactional. Before attempting a change, the backend captures a compact description of the current state—the venv directory and detected version. It then finalizes the current interpreter, applies the new target and logs in. If initialization fails for any reason, the backend finalizes again and restores the previous state, re‑logging in and restoring the prior version record on success. This simple but strict “switch‑or‑rollback” contract prevents half‑initialized sessions and ensures the host remains usable regardless of individual switch failures.


Operational visibility matters both for diagnostics and for UI integration. The backend publishes getters for the current venv directory, the detected Python version, and the chosen interpreter path. It can also discover virtual environments by scanning starting directories for pyvenv.cfg and recognizable layout patterns, returning a list of environment paths with associated versions. For consumption by other components, structured formats such as JSON simplify parsing and future evolution; even when initial implementations return human‑readable strings, migrating to a structured schema pays off quickly.


Several pitfalls recur in real deployments. Symlinked venvs must be treated carefully to avoid collapsing into system paths during resolution. PATH must be rebuilt from an original baseline to avoid unbounded growth during rapid switching. On Py≥3.8, the system interpreter should be initialized with explicit module search paths rather than relying on implicit platform logic. On Windows, hard‑coded “C:/Python” roots are fragile; build paths from CMake‑injected PYTHON_STDLIB/PYTHON_SITELIB or query sysconfig from a known interpreter. Finally, enforcing a stable major.minor within a process, while conservative, prevents obscure ABI issues that are otherwise difficult to reproduce.


A typical backend sequence for switching to a new venv reads cleanly: accept a target path, resolve it to either a venv or the system interpreter, finalize the current interpreter, set the new Python home and program name or PyConfig fields as appropriate, initialize, publish paths, and report success. If any step fails, finalize immediately and restore the previous environment. Switching to the system interpreter follows the same template, with the additional recommendation to populate module_search_paths explicitly for Py≥3.8. Querying the active environment simply returns the cached directory, version, and executable path.


A robust runtime venv switcher is primarily a matter of careful engineering rather than novel algorithms. By unifying input semantics, discovering versions reliably, choosing the correct embedding API for the runtime, treating the host environment and sys.path as controlled resources, and insisting on transactional switching with rollback, the backend achieves predictable, production‑grade behavior without sacrificing flexibility.


Implementation of Python interpreter hot switching in Cantor backend architecture

In Cantor’s backend architecture, the Python interpreter is embedded in a long‑running service process, and the frontend communicates with it via a lightweight protocol over standard input and output. The essence of runtime virtual‑environment switching is not to replace this service process but to terminate the current interpreter and reinitialize a new interpreter context within the same process, thereby avoiding any rebuild of the frontend‑backend communication channel. This approach requires a stable message protocol, controllable interpreter lifecycle management, consistent cross‑platform path and environment injection, and compatibility constraints combined with transactional rollback at the version level to ensure safety and observability during switching.

The message protocol adopts a framed “command–response” model with explicit separators and covers environment switching, environment query, and environment discovery. When a switch is initiated, the frontend issues the switching command and immediately follows with an environment‑information query to validate the state and synchronize the UI. Upon receiving the command, the service process first resolves the target environment, accepting either a virtual‑environment root directory or an interpreter executable, normalizing both into an environment root and interpreter path, while avoiding misclassification of system directories as virtual environments. Environment detection adheres to cross‑platform structural conventions: pyvenv.cfg and bin/python[3] on Unix‑like systems, Scripts/python.exe and conda‑meta on Windows.

The interpreter “hot‑switch” follows an explicit lifecycle sequence: finalize the current interpreter, then initialize a new one. For Python 3.8 and later, the PyConfig isolated‑initialization path is used with explicit settings for the executable, base_executable, home, and module_search_paths to minimize external interference; for earlier versions, traditional APIs are used in conjunction with environment variable and sys.path injection. To ensure semantic equivalence with terminal‑based environment activation, sys.prefix and sys.exec_prefix are rebuilt, module search paths are reconstructed, and key variables such as VIRTUAL_ENV, PATH, PYTHONHOME, and PYTHONPATH are injected when entering the new environment and cleaned when reverting to the system environment.

The compatibility policy enforces equality on the major.minor version. After the first successful initialization, the initialized interpreter version is recorded; subsequent switches are permitted only to environments with the same major.minor, mitigating uncertainty introduced by cross‑version ABI or interpreter‑state differences. The switching operation is transactional: prior to finalization, the current environment and version are cached; if initializing the new environment fails, the system automatically rolls back to the previous environment and restores version information, ensuring the server remains available under exceptional conditions. Observability is provided by returning key details—environment root, interpreter path, and version—through the query command, enabling UI presentation and traceability at interpreter granularity; diagnostic outputs are produced on critical paths such as version mismatch, initialization failure, and environment restoration to facilitate investigation of cross‑platform and resolution issues.

The Settings page’s interpreter selector uses a “lazy‑load plus runtime cache” strategy. On first entry, it recursively scans the user directory and conventional locations, deduplicating and classifying environments based on structural markers and version probing; immediately after rendering, it asynchronously requests the backend’s current environment, and if no response arrives within a bounded timeframe, it falls back to locally detecting the active interpreter to ensure sensible defaults in both the drop‑down and input field. To avoid UI jitter, switching is triggered by an explicit confirm/apply action; once applied, an environment‑change signal is emitted, the session layer issues a combined “switch plus query” command to complete the closed loop, and the results are fed back to the UI. Both success and failure are reported in a uniform response format; on failure, the Settings page raises a one‑time warning for the dialog session and automatically realigns to the last known‑good environment to preserve a stable user experience.

In typical usage, providing an absolute interpreter path is recommended for its determinism and cross‑platform clarity; supplying a virtual‑environment root is also supported, and the system will resolve the corresponding interpreter automatically. Returning to the system interpreter can be achieved via an empty path or a dedicated “system interpreter” option in the UI; the backend will clear injected variables and restore system path semantics. When switching across minor versions is required, a more robust practice is to manage backend instances at the major.minor granularity—or to separate them explicitly in the UI—to reduce the frequency of rollbacks and perceived interruptions.

The end‑to‑end interaction sequence and the Settings page “discover–compare–align–apply” workflow are illustrated by the two diagrams above. The former depicts message exchange and lifecycle management across the Settings page, session layer, service process, and embedded interpreter; the latter details environment enumeration, validation, backend alignment, and user confirmation. Together they constitute an engineering‑grade runtime virtual‑environment switching loop that balances stability, cross‑platform consistency, and observability, meeting both interaction and maintainability requirements.


Pic 6. End-to-end timing of runtime switching


Pic 7.  Set up the "Discover-Compare-Align-Apply" workflow on the page



How to switch Python virtual environment through cantor

1. When you open Cantor, if you do not select a virtual environment in the General Tab of Configure Cantor, the Python in the current system will be opened by default. You can get the environment linked to the current Python interpreter by entering "sys.path"

2. 
Open the General Tab of Configure Cantor. You can use the top two options to choose to manually import through the folder (the default is to perform a 5-level recursive search) or manually select the Python interpreter to import the virtual environment.

3. Select the virtual environment you want to switch to and click "Apply" to switch to the new environment


4. Enter "sys.path" again to verify


5. If you select the wrong virtual environment version, the system will prompt an error
                                          

6. If the environment switch fails, the program will fall back to the last successfully switched environment, which is "venv1" in this test