Skip to content

Thursday, 18 September 2025

 
Akademy 2025 group photo.  

This year's Akademy took place in Berlin, Germany. The city's modernity and avant-garde counterculture atmosphere fit very well with the current mood in the KDE community. As volunteer developers push forward with technologies that even multi-billion dollar corporations struggle to imitate, there is a general feeling that something great is on the horizon. With major migrations and potential adoptions in public administrations, not to mention KDE technologies making their way into all kinds of devices, it seems that we are finally on the verge of going mainstream and bringing FLOSS to the general public.

Bearing all this in mind, the community gathered at Berlin's Technische Universität for an intense week of KDE-related activities.

Saturday, 6 September 2025

On Saturday morning, and as is traditional, Aleix Pol, President of KDE e.V., kicked proceedings off at 9:30 sharp. Aleix told us about what we should expect, reminded us about accreditations and lanyards. He also told us about the where the talks, BoFs, coffee breaks and sponsor booths were for when we needed a break.

 
Aleix Pol officially opens Akademy.  

And with that we were off!

In our first keynote, Alexander Rosenthal, project leader at DigitalHub.SH in the northern German state of Schleswig-Holstein, explained the exciting migration to Free Software going on in the region.

The focus was mainly on digital sovereignty and how public administrations should look to FLOSS to recover ownership of their infrastructures, devices and data, but the bit that drew the loudest applause was when Alexander mentioned that Plasma was the front-runner to power the institutional desktops in the region.

 
Alexander Rosenthal tells attendees about what is going with Schleswig-Holstein's migration to Free Software.  

After the coffee break, David Edmundson talked about Plasma's reputation, how several mistakes had marked it for a long time, how we have overcome the bad press and what we can do to move forward and avoid the same pitfalls again.

In room 2, Karanjot Singh told us about KEcoLab, an automation tool for energy consumption measurements that allows KDE developers to remotely measure the energy consumption of their KDE software through GitLab CI/CD.

This means that Instead of obtaining measurements manually and in person in a lab such as the one at KDAB, Berlin, KDE developers can trigger the process through CI/CD, wherever they are. This enables developers to effortlessly assess their software's energy consumption when merging new code into the codebase.

After that, and back in room 1, Andy Betts presented the latest in the design goals presented for the first time at last year's Akademy. Andy provided a list of all the updates and implementations including a review on variable availability for designers and developers.

 
Artist's impression of KDE Linux.  

Meanwhile, in room 2, everyone was excitedly listening to Harald Sitter release the first alpha version of KDE Linux. Based on an idea launched back during Akademy 2024, KDE's reference distro for KDE technologies is now in a good enough state for it to be easily tested.

 
Harald Sitter mit Bananen.  

A little after 2 pm, Bettina Louis, Carolina Silva Rode, Joseph De Veaugh-Geiss, and Nicole Teale took to the stage in room 1 to tell us about how the End of 10 campaign is going. The answer was "very well". Designed by the KDE Eco team to try and curb the environmental disaster that Microsoft's end of support for Windows 10 will entail, the campaign encourages users not to ditch their older machines that do not support Windows 11, and do a real upgrade and install Linux instead.

The campaign caught the FLOSS community's imagination and has sparked installfests, inspired news stories, and revitalised repair cafés all over the world.

At the same time in room 2, Cristián Maureira-Fredes from The Qt Company told us about how bindings to newer languages, like Qt for Python have been around to open the doors to new generations of developers, and how one of the Qt's goals was to find ways of unlocking their framework’s features for even more programming languages without the need to rely on bindings or learning C++.

At 15:35 we had the traditional Report of the Board in room 1. KDE e.V. board members Adriaan De Groot, Aleix Pol González, Eike Hein, Lydia Pintscher and Nate Graham talked about the work of the organization over the past year and what is coming next.

This session was followed by the annual report of the Working Groups, led by Lydia Pintscher. The Working Groups help the KDE Community in various areas such as fundraising, community management and running our infrastructure.

The third session in this vein was the KDE Goals - One year recap. Farid Abdelnour, Nicolas Fella, Jakob Petsovits, Gernot Schiller and Paul Brown talked about how the KDE goals that were set at Akademy 2024 were going one year on.

Meanwhile, in room 2, Arjen Hiemstra was discussing buttons, sidebars and other graphical elements and how they get rendered in KDE applications during his talk on the Union styling system. Arjen introduced Union at Akademy 2024 and the project aims to create a styling engine that unifies the various styling methods used in KDE. Arjen covered the progress made to achieve this goal and some of the new major features that have been developed, the state of adopting Union within KDE and some plans for the future.

Arjen was followed by Kevin Ottens, who talked about the progress made in the "KDE Neon Core" project, and effort to bring Plasma to Ubuntu Core.

And then Alexandra Betouni took to the stage and talked of her real-life experience trying to claim a space in the male-dominated tech industry.

At 18:00, Nicolas Fella was looking at KDE Frameworks' bindings to other languages (apart from C++), such as Python and Rust. Nicolas explained why this was important, how the binding generation worked under the hood and how they can be used in applications

In room 1, lightning talks, talks that last between 5 and 10 minutes, were kicking off, opening with Emilia Valkonen-Damjanovic, who introduced attendees to Qt Academy and the future plans for an official Qt developer certifications.

She was followed by Marco Martin, who tackled the controversial idea of retiring KWallet, a venerable app that needs to be superseded by a more modern approach to password safety.

Then Volker Krause talked about how to implement emergency and weather alerts into free software systems. Interestingly Volker's implementation in kpublicalerts was put to the test during the BoF when the citizens of Berlin received an alert regarding bad weather. Ultimately, the emergency alert system will be built into Plasma/Plasma Mobile itself, as it should not have to rely on an external app.

Finally Alexander Lohnau gave us an overview of Clazy, KDE's static code analyzer for Qt and C++, and how it can boost developers' workflow, helping them write cleaner, faster, and more reliable code.

Sunday, 7 September 2025

The first session on Sunday started at 10:00 and featured Paloma Oliveira from the Sovereign Tech Agency who spoke about how KDE could become more diverse, not by implementing rigid rules, but with “gentle enforcement”, by establishing communication patterns, governance models, and accountability mechanisms that help communities grow in more just and inclusive directions.

 
Paloma Oliveira explains how soft power is effective to encourage a shift towards diversification and inclusivity.  

After a quick break, we were back in room 1 with Akseli Lahtinen, who spoke from the heart of his experience on how he had been badmouthed, harassed and received hate mail just for implementing features or trying to improve KDE's UIs, and how he had handled it.

In room 2, Sune Stolborg Vuorela told us about CppCheck, a static code analyzer that can be integrated into KDE's CI workflow on invent, and how to get properly started with it.

After lunch, Akademy participants posed for the Akademy 2025 group photo and then went on to listen to David Edmundson, who explained that, while attracting contributors to project is relatively simple, recruiting maintainers is less so. He then gave advice on how to do just that.

 
David Edmundson talks about how to recruit project maintainers.  

At the same time, Aleix Pol was in room 2 talking about Flatpak and dished out advice on how to make Flatpak an actual environment where applications are developed.

Later, in room 1, Till Adam, of KDAB, tackled the thorny subject of FLOSS and business. Drawing from his own experience, Till expounded on the dos and don'ts of growing an enterprise from community roots.

Another interesting business-related talk by Patrick Fitzgerald followed. Patrick explained strategies for massive migrations from Windows to Linux, the potential pitfalls migrators face, and how to come out on top in the end.

In room 2, Ulf Hermann covered the new technologies coming to Qt 6.10 that allow developers to expose data to QML.

This was followed up by Nicolas Fella, who explained how developers can leverage the new QDoc system to generate better documentation for their projects.

Back in room 1, Nate Graham was telling us how, even though the world is a mess right now, that same chaos opened up opportunities for projects like KDE and how the community could take advantage of them.

In room 2, Neal Gompa presented a brief history of Fedora KDE Plasma Desktop Edition, success story of how community pressed for a distro to become a recognized flagship offering, and what happens next.

Then, artist and developer Joshus Goins looked at the steps taken in Plasma 6 to make KDE's desktop artist friendly, and how things will go from here on onwards.

At 17:15, lightning talks were starting up again in room 1, and Nicolas Fella kicked them off explaining the theory and practice of effective intra-team communication.

David Edmundson followed, continuing with the theme of communication, and told us what makes an effective commit message.

Bhushan Shah was up next with a talk about the state of power management in Plasma Mobile.

And finally, Jean-Baptiste Mardelle told us about Kdenlive's fundraising efforts and plans, where the money goes, and what they intend to fund next.

Then it was turn for the sponsor's talks and representatives from The Qt Group, KDAB, openSUSE, and Enioka Haute Couture took to the stage to tell attendees about their companies and how they are involved in KDE.

Aleix Pol stood in for Canonical, as the rep who was supposed to be at the event was sick and could not make it.

The last on-site of the day was the KDE Award ceremony. Vlad Zahorodnii and Xaver Hugl were awarded for their work on KWin and Wayland. Then Alexander Lohnau received an award for his work on Frameworks, Clazy and Krunner. Allen Winter was presented for an award in absentia for his work on KDE PIM and many years of contributions to other projects.

Finally, Kieryn Darkwater received an award in the name of all the Akademy organisers for organising such a great Akademy.

 
Albert Astals presents Kieryn Darkwater with an award for organizing an excellent Akademy.  

For the after-dark track of Akademy, attendees retired to c-base, Berlin's repurposed crashed space station and hackerspace, to discuss, relax and enjoy the community vibe.

Here are the new modules available in the Plasma 6.5 beta:

  • knighttime

Some important features and changes included in 6.5 beta are highlighted on KDE community wiki page.

View full changelog

Tuesday, 16 September 2025

Visiting my parents before going to Akademy I found the manual of my first Linux distribution ever: S.u.S.E. Linux 5.1 from 1997.

S.u.S.E. Linux 5.1 Manual

Given my first PC was some Pentium 100 with 16 MB of main memory and Windows 95, that was my first dive into open source software.

Seems I went in less than 2 years from starting on Windows to at least trying out Linux.

A few years later I joined the great KDE community and stayed there :)

:) Now I feel old.

Feedback

reddit - Do you still remember your first Linux distribution?

People have been a bit weirded by what happened during this period. People have been interested in this after the post by Jonathan Riddell was written.

Here's what I remember from the situation.

Order of events

  1. I get laid off from previous job in 2023-10-10.
  2. Nate Graham learns of this and invites me to join Blue Systems.
    • I would be joining the team that works with Valve contracts. Cool!
    • He had no power to really "invite" me but told me that there's a good spot I could fill.
    • I apply and cross my fingers
    • I get into interview and get hired!
  3. I work for Blue Systems until ~31.10.2024, renewing the contract yearly.
  4. Blue Systems holds a get-together in late 2024.
  5. I am really bad with traveling due to anxiety and I was still recovering from the Akademy 2024, I skip this get-together.
  6. I get a video call from the get-together, where I'm told news: the team that has the Valve contract is being laid off.
  7. However! We also have this chance to start working with our single contract, Valve, in separate company.
    • Essentially, we, the team that works with Valve, are moving to new company and continue this contract under it's name.
  8. A lot of chatter in work Telegram about how the new company should be governed.
  9. A lot of differences in opinions. Long debates. Talks.
    • Everyone had their own opinions and plans.
  10. My wife gets really sick and has to spend chunk of December in hospital.
    • I have zero energy for any of the work governance things.
    • I just wanted to make sure I have job and get my salary.
    • Thus, I did not really care what the plan was going to be.
  11. Eventually, we decide Nate's plan for this is the easiest.
    • More info here Personal and professional updates — announcing Techpaladin Software
    • I would have been fine with whatever plan, as long as I get to keep my job and continue my work and getting paid.
    • I do not remember any kind of voting or anything like that. We just went with it. Everyone was very tired of this weird situation.
    • We all just wanted to get back to work.
  12. Time goes by and TechPaladin is ready for continuing the work around March.
  13. I have some small interview about the situation with Nate, and he asks me if I'm still interested to join.
    • No contract was made yet with anyone. TechPaladin was barely established at this point.
    • I assume everyone in Valve team went through this discussion. I do not know anything else.
    • We had no money to pay for non-Valve related work.
  14. I end my contract with Blue Systems around March 2025.
  15. I start my contract with TechPaladin around same week.
  16. I still work there!

Messy.

Yeah, it's really messy. And I wish it had gone differently. But that's how things go.

I have been laid off before, twice.

First time when I was a very fresh programmer, I was suddenly told in middle of day that my contract will end right before my probation period will end.

Then second time, I went for a leave due to burnout, come back to work, and on same day I'm told I'm getting laid off.

It sucked.

So Jonathan, I can sympathize with your feelings about the situation. I hope you get help for your issues and warmth to your life. Sincerely. I do not have anything bad against you.

My experiences at TechPaladin

  • TechPaladin has always paid my salary in time.
  • My contract is completely legal where I live.
    • I'm sure I could negotiate it into something more shiny with help of lawyers, money and time.
    • But I trust TechPaladin to not screw me over.
    • And if they would do so, I would leave immediately and cut my losses.
  • I've never been "abused" in any way.
    • People care about me there.
    • They notice if I'm burning out (looking at you Dolphin) and help me switch projects.

Sure, I might be naive for trusting a company like this. But I'm just like this. If I like the people who I work with, I trust them. This is not the first company I trust like this. It won't be the last either, probably.

And I just want to fix bugs in KDE software without having to think about the corporate stuff, but still have money for food and rent. TechPaladin lets me do that.

In the end, the contract is my choice and this is the choice I've made. So far so good.

And my friends and family know me. If TechPaladin would go against my values or rights, all of them would know.

Conclusions

Draw your own conclusions.

Or better yet, join KDE to help keep the project running: https://community.kde.org/Get_Involved

Maybe this post helps shine light on things, anyway. Not that I really need to do this, since it's all private matters, but since it's been blown open, meh. Might as well. I'm tired of the misinformation I've seen around.

glhf.

ps. if some youtuber reads this, hi

By now, many have probably read Jonathan Riddell’s blog post yesterday about his departure from KDE and the events that led up to it. And today, an article has been published in ItsFoss about the topic that unfortunately seems to have misunderstood some of the details of Jonathan’s post. I didn’t want to have to write this post, but since I’m named personally in both places, and there are inaccuracies spreading out there, I thought it would make sense to correct the record before this becomes too much of a game of telephone.


Overall it’s a very sad situation, and I want to make it clear that I bear no malice or ill will towards Jonathan. As for the internal details of the transition of many of Blue Systems’ personnel to Techpaladin Software, Jonathan is entitled to his interpretation of events, and that’s fair. But it was a delicate transition, and people also have a right to personal and professional privacy. As such, it wouldn’t be appropriate for me to get into anything non-public about individual people’s personal and work situations, motivations, or decisions.

So there are some parts of the story that are going to have to go un-told in public, at least by me. But I can correct the record about misunderstandings and errors, and offer my own perspective!


I’ll start with the ItsFoss article:

First of all, Jonathan never wrote in his post that he saw KDE itself “moving away from the cooperative and transparent model he valued”, or that decision-making was increasingly concentrated under me. Jonathan’s blog post wrote about his relationship with me in the context of the Blue Systems to Techpaladin transition, not about KDE itself. KDE itself is fine — better than fine, really. KDE is thriving, with competitive board elections this year and an increased base of donations it can use to assert a measure of independence from commercial entities.

It’s also not true that Techpaladin was “meant to continue supporting KDE neon after Blue Systems scaled back.” Jonathan didn’t write this, and it was never the case. Techpaladin was and is a vehicle to take over the contract work that Blue Systems had been engaging in, after its manager made the decision to discontinue that work. KDE neon was never a part of this.


There are also a few topics from Jonathan’s original post that need addressing. First of all, Blue Systems is not shutting down. I was just talking to a happy Blue Systems person at Akademy last week. As I mentioned, what actually happened is that Blue Systems divested itself of the consultancy element that it had picked up a few years prior. Blue Systems is still around as a company, and is still employing people who want to work there. Several people opted to stay there rather than moving over to Techpaladin. And at no point was I or Techpaladin ever Jonathan’s employer.

I also want to make it 100% clear that I never made any effort to shut Jonathan out of anything in KDE, never encouraged anyone else to cut off contact with Jonathan or shut him out of anything in KDE, and never pulled strings behind the scenes to make it happen without looking like it was happening. Nothing of the sort! If Jonathan came back to KDE, I would be happy to rebuild a collegial relationship over topics of shared interest.

Finally, regarding company structure and employment details, I went into this a few months ago in https://pointieststick.com/2025/03/10/personal-and-professional-updates-announcing-techpaladin-software/#comment-40233. Techpaladin’s current company structure actually allows people interested in making it more “co-op-like” to buy their way into partnership, which is how Igalia — the “cooperative socialist paradise” that Jonathan mentioned — does this as well. I’m confident that no laws are being broken here; I exhaustively researched the employment laws of 7 countries and then confirmed my conclusions with a lawyer before moving forward with anything. In addition, we practice financial transparency and cooperative decision-making on the topics of hiring, new contracts, etc. So internally it’s quite “co-op like” already.

But this is my first time co-running a company of this size and complexity, so I’m sure I’m making mistakes and can do better. Now that the company has survived the initial setup and been around for 7 months, there’s some breathing room to explore that. But from the start, the whole point has been to put money into the hands of people doing extraordinary KDE work that makes the world a better place, and to provide all of us with more agency over our KDE careers.


Hopefully all of this makes sense. If anyone has any further questions, I’d encourage them to ask publicly or privately, and I’ll do my best to answer what I can that doesn’t compromise anyone’s personal or professional privacy.

This post is long winded and unfocused. If you prefer to not experience my rambling, this is your warning. If you do, enjoy!

Pre-Akademy Akademy-ing

Hello! I was at Akademy 2025! Along with Devin and Derek, this trip was actually part of a larger grad trip, the ending of which happened to conveniently line up with Akademy. The last time I attended Akademy was back in 2022, so I was excited to be back!

Flying out of Hong Kong airport to Berlin onboard Turkish Airlines

The last part of the grad trip was in Hong Kong, so naturally that’s where the journey to Akademy begins! We flew out using Turkish Airlines (a first for me), stopping over in Istanbul. I will say, the economy seats do come with a nice amenities kit:

In any case, after a brief (and really expensive! the food was not cheap!) layover in Istanbul, we arrive in Berlin! It was night by the time we got into the hotel, so we got some snacks and then headed to bed.

The first full day was all about getting setup, doing a bit of exploring, then heading out to the evenings main event: the welcome event!

The Pixel 3a was my Plasma Mobile device of choice for this Akademy (I found it in a Hard Off store in Japan during the trip!), and since Derek was getting a SIM for his phone, I decided to also get one for the Pixel. This lead to a pretty entertaining sequence of pulling out a Plasma Mobile device when the employee offered to install the SIM for me.

It all worked out, though! I mean, I ended up doing most of the work actually navigating the OS (they looked somewhat concerned when I rebooted the phone to get the SIM card recognized and the tty sped by), and they had to take a moment to look up the APN settings (probably an uncommon occurrence for them), but the SIM installed and I was connected to the Vodefone network without any drama.

The Brandenburg Gate
A classic Quebec moment, a Quebec government office near the Brandenburg gate. It’s not a Canadian embassy!
Ah, the Europeans sure do love their institutions. I do, too!

Of course, before heading back to the hotel room, we had to indulge ourselves with some local Berlin cuisine, which means currywurst. (for those not aware, it’s sausage and fries with ketchup, mayo and curry powder).

The currywurst was actually really good, we got more the next day too.

Back in the hotel room now, and you would think maybe we’d take a nap or otherwise rest. But nope, my hotel room instead became a place to lock in. After all, there were things that needed to be done! I for one spent the time flashing a fresh copy of postmarketOS on to my Anbernic RG35xx.

Akademy Welcome Event

Finally, it is time to go to the welcome event! Being in the Plasma Mobile sphere, naturally I sat with various Plasma Mobile and adjacent people. Overall a fun night, lot’s of excitement in the air with people meeting each other (some for the first time, some after years), and of course excitement for the week of Akademy to come. Also naturally, the table was full of drinks, food, and Plasma Mobile devices.

Akademy Day 1

Talks, talks, and more talks! That is the name of the game for the first two days of Akademy. Taking place in university lecture halls, they cover a wide variety of KDE related topics and are really interesting to listen to. There are also a lunch break, plus some coffee breaks sprinkled in.

During the day, there are usually two talks running in parallel in the two rooms, so you need to pick one. These are the talks I attended on Day 1:

  • Keynote: Open by Design: How Governments Can Lead the Shift to Digital Sovereignty (Alexander Rosenthal)
  • Plasma: Lessons Learned and Our Path Forward in 2026 and Beyond (David Edmundson)
  • KDE Linux: Banana Growth Cycle (Harald Sitter)
  • The Role of New Languages in the Future of the Qt Ecosystem (Dr Cristián Maureira-Fredes)
  • Report of the Board, Report of the Working Groups
  • KDE Goals – One Year Recap
  • Lightning talks: What’s New with Qt Learning? (Emilia Valkonen-Damjanovic), Gently Retiring KWallet (Marco Martin), Emergency and Weather Alerts (Volker Krause), Boosting your code and simplifying your life with clazy (Alexander Lohnau)

If any (or all) of those titles seem interesting to you, you can check out the Akademy program website for full descriptions. At some point the talks will also be edited and posted online as well.

In the little bits of time I managed to get during the day, I did get some work done as well! A handful of improvements to KRetro (my Libretro frontend for Plasma) and some hacking on Plasma Mobile.

After the talks finished for the day, people headed home or out to eat dinner in various groups. I went with one such group and had some really tasty vegan thai curry!

mmm delicious

Akademy Day 2

Wake up! It’s a new day, more talks! You don’t want to miss it! (I might have slept in a bit)

Day 2 is structured much the same as day 1, with the exception of the group photo! Always a fun time seeing everyone gathered together in one space.

The talks I attended on day 2 were:

  • Handling Negative Feedback (Akseli Lahtinen)
  • Developing on Flatpak (Aleix Pol Gonzalez)
  • Getting Hired to Work on FOSS – The Do-s, Don’t-s and Pitfalls for Everyone Involved (Till Adam)
  • Next-Gen Documentation Infrastructure for KDE (Nicolas Fella)
  • Fedora KDE Plasma Desktop Edition is Real, Now What? (Neal Gompa)
  • Lightning talks: Say what now? – Communication Theory for Software Developers (Nicolas Fella), The Art of a Good Commit Message (David Edmundson), Plasma Mobile Power Management: Reliable Sleep and Wake Ups (Bhushan Shah), Funding and Growing – Kdenlive Experiences and Perspectives (Jean-Baptiste Mardelle)

And would you look at that, the talks are done! Blink and you’ll miss ’em. Honestly it felt like they went by so fast, it’s lots of fun getting to see all the KDE community members you see online in person, and getting to hear all the cool things they are involved in.

The rest of Akademy week?

After the saturday and sunday full of talks, the rest of Akademy week is dedicated to various activities, notably BoFs (birds-of-a-feather). They are essentially meetings of different interests (usually of specific topics, for example there is a BoF for Plasma Keyboard). These represent a pretty important part of Akademy, all these developers and community members from around the world are in one place, and so you get the unique opportunity to have in person meetings, discussions and planning. Besides BoFs, there are also various workshops and trainings held as well. Plus, there is the day trip in the middle of the week!

Unfortunately I could not attend the rest of Akademy due to timing with other obligations, and so it was time to finally head back home and bring the trip to a close.

Goodbye, Akademy!

The 3 days of Akademy were a lot of fun! It was great to meet fellow KDE community members and hear about all they had to say in the talks. I look forward to hopefully attending Akademy again next year!

Monday, 15 September 2025

Qt Multimedia is getting its own category at the Qt Forums!

As accessibility continues to gain traction across major operating systems, high contrast mode has become a key feature for improving visual clarity and usability. With the release of Qt 6.10, applications built with Qt now readily get support for high contrast modeacross multiple platforms, ensuring more inclusive and visually adaptive UIs. In this post, I explore how Qt 6.10 supports high contrast mode, what it means across different systems, and what Qt now provides for built-in styles.

Enhancing Accessibility with Better Contrast

In recent years, major platforms such as Windows 11, macOS, and the Gnome desktop have introduced settings for a high contrast mode that enhances the contrast between foreground and background user interface elements. Adjusting these contrast settings typically updates the system color scheme and, in some cases, adds more pronounced outlines or other modifications to native UI controls. On macOS, iOS, and Gnome, users can enable increased contrast through a toggle in the accessibility section of the system settings, which reinforces outlines for most native controls. Windows 11 takes a more advanced approach with its Contrast themes feature, resulting in significant changes to the appearance of native applications when activated.

New with the High Contrast Mode in Qt 6.10 

KWin Gamepad Plugin: Weeks 3-4

Picking up from weeks 1+2 ( research + prototypes with libevdev/uinput ), these two past weeks were about moving from “research-only mode” to turning ideas into programming logic that lives inside KWin to: detect gaming controllers and their input events, keeps Plasma awake on controller activity, handles hot-plug and pre-existing connections on startup, and lays down the first mappings from controller input to keyboard/mouse actions without stepping on other apps utilizing the controllers.

From the start my mentors and I have had a general idea of the features we wanted to add but weren't too sure how to implement them. After some thinking and experimenting they advised me to start off with a KWin::Plugin. This would allow us to start introducing the gaming controller functionalities to KWin while avoiding having to edit the core or guts of KWin. It would also be a great entry point for current and future game controller input objectives, allowing us to start small with a 1st party KWin plugin, build on it, and possibly integrate it into core functionality.

When it comes to creating KWin plugins I had a few options:

  • Scripts: Written in QML/JavaScript and used for automating window management, tiling, shortcuts, etc.
  • Effects: Implement visual effects on windows, the desktop, or transitions.
  • Core/Native: These are built into KWin itself and extend KWin’s internal functionality.

Since the plugin needs low-level device access, such as monitoring /dev/input/event*, listening to udev hotplugs, opening fds, and reacting to evdev events the best choice was to go with Core / Native plugin. As opposed to Effect and Script plugins which aren’t designed to open devices or do long-running I/O, most simply just live inside the rendering/scripting layers.

I started off by searching for an example of how to build a KWin plugin so I could start learning how to build my own. Thankfully my mentor @zamundaaa provided me with some great examples:

  • Example / Tutorial plugin located in src/plugin/examples/plugin
  • Screenshots plugin located in src/plugins

Between both of these examples and mentoring I was able to piece together the scaffolding ( essential parts ) of a KWin plugin and was able to put together the first version of this plugin, gamepad plugin, located in: kwin/src/plugins/gamepad. At this point the plugin is structured as follows:

main.cpp // Entry point & Defines GamepadManagerFactory Class
metadata.json // Declares the plugin to KWin, define information about plugin
CMakeLists.txt // C++ Build/Installation/Logging wiring
gamepadManager.{cpp/h} // Plugin Logic: Defines GamepadManager Class
gamepadManager.{cpp/h} // Plugin Logic: Defines Gamepad Class

Implementation notes

GamepadManagerFactory

GamepadManagerFactory Class serves simply as the entry point for the plugin. It's a factory class, or a class used to create other classes / object types. Like the examples, it inherits from PluginFactory and declares it as its interface as well as pointing to the metadata.json file for this plugin. It initializes the plugin through its create() function which returns a GamepadManager.

GamepadManager

GamepadManager class serves as the central coordinator (the “brain” or “hub”) of the entire project. While creating this I took a lot of inspiration from src/backend/drm/drm_backend.{cpp/h}, which itself is responsible for handling drm/gpu devices. GamepadManager covers many responsibilities. It owns and manages all gamepad devices, handles discovery (startup enumeration, hot-plug), lifecycle (adding/removing), and communication (signals when pads are added/removed, or when their state changes). Overall its responsible for keeping track of the current set of controllers and their status.

Detect hot-plug and pre-existing device detection:

For this part many of the DRM backend pattern were used. The first thing the manager class does on initialization is create two QMetaObject::Connections that monitor the current KWin session for devicePaused and deviceResumed signals. This helps track devices when Plasma goes in and out of sleep/suspend which causes devices to be Paused and Resumed. It then enumerates over all event devices located in /dev/input/event* to handle any pre-existing connections to game controllers. If it discovers an event device it adds the gamepad ( start tracking it and its input ).

// On init:
// Enumerate current input nodes to filter and add ONLY event nodes
QDir dir(QStringLiteral("/dev/input"));
const auto files = dir.entryList({QStringLiteral("event*")}, QDir::Files | QDir::Readable | QDir::System);
for (const QString &file : files) {
 const QString path = dir.absoluteFilePath(file);
 if (!isTracked(path)) {
 addGamepad(path);
 }
}

Finally using udev it monitors the subsystems and filter for only "input" subsystem events. It uses QSocketNotifier to produce signal notifications from udev events and creates a connections between that notifier and a memeber function, handleUdevEvent, that handles events coming from the udev monitor when an input device is detecetd. Some checks are performed to verify if the device is a gaming controller, such as expected input events and input event types. This include input events like BTN_JOYSTICK and BTN_GAMEPAD, which are commonly defined in gaming controllers. As well as checking for joystick or D-pad capabilities. If the checks pass the game controller is "added", or in other words, the device is wrapped in a Gamepad class, kept track of and its presence monitored.

// setup udevMonitor
if (m_udevMonitor) {
 m_udevMonitor->filterSubsystemDevType("input");
 const int fd = m_udevMonitor->fd();
 if (fd != -1) {
 m_socketNotifier = std::make_unique<QSocketNotifier>(fd, QSocketNotifier::Read);
 connect(m_socketNotifier.get(), &QSocketNotifier::activated, this, &GamepadManager::handleUdevEvent);
 m_udevMonitor->enable();
 }
}

Gamepad

Gamepad is a wrapper class. It's purpose is to be tied to a physical controller. One Gamepad object per physical game controller. This enables quick access/reference to the device and allows for the physical controller to be treated like an object. This class is also responsible for device input handling, Plasma Idle refresh, and button to keyboard/mouse mappings. In the future things might get split up into seperate files but as it is, it handles a lot. As with the GamepadManager, this class takes a lot of inspiration from DRM backend patterns.

Detect Input Events:

Once a gaming controller device is detected it gets wrapped in a Gamepad class object. Which in turn wraps the controller in a libevdev object pointer. This is the part that gives access to the controller through the libevdev API, making it easier to work with it and monitor its input events. Like GamepadManager the first thing this class does is use QSocketNotifier to produce notifications from the controllers fd, i.e monitor for input. It then creates a connections between that notifier and a member function, handleEvdevEvent, which handles all incoming input events from that device.

libevdev *evdev = createEvDevice();
if (evdev) {
 m_evdev.reset(evdev);

 m_notifier = std::make_unique<QSocketNotifier>(m_fd, QSocketNotifier::Read, this);
 connect(m_notifier.get(), &QSocketNotifier::activated, this, &Gamepad::handleEvdevEvent);

 qCDebug(KWIN_GAMEPAD) << "Connected to Gamepad ( new libevdev* ): " << libevdev_get_name(m_evdev.get()) << "at" << m_path;
}

Plasma Idle Refresh On Controller Activity

With the ability to monitor for all input events from the device, the plugin then uses that information to know when to reset Plasma idle timer. For this Gamepad imports/includes input.h file and makes a call to input()->simulateUserActivity() when an input event is detected from the controller. This causes Plasma idle timer to be reset and prevents the system from going into sleep/suspend mode while using only gaming controller.

// reset idle time
input()->simulateUserActivity();

Controller -> Keyboard & Mouse Mapping

Gamepad uses API function from libevdev to check for input events, identify the specific input event and map that to a keyboard or mouse input event. Using libevdev_next_event() it checks for the input event coming from that game controller. It then identifies the specific input event through its input event type, code, and value. To simulate a mouse and keyboard the core/inputdevice.h file is imported and used to declare GenericInputDevice which inherits from InputDevice. That GenericInputDevice effectively behaves like a virtual keyboard and mouse inside KWin’s input stack.

When specific libevdev input event are identified, such as EV_KEY + BTN_SOUTH ( A button press ) OR EV_KEY + BTN_EAST ( B button press ), it call InputDevice::sendKey() to simulate keyboard key press and inject the desired keys into KWin input pipeline. In this case Enter for A ( BTN_SOUTH ) and Escape for B ( BTN_EAST ). To emulate mouse/pointer the plugin makes calls to InputDevice::sendPointerButton() for left and right mouse buttons, and InputDevice::sendPointerMotionDelta() for pointer movement.

architecture_diagram_0
architecture_diagram_1
architecture_diagram_2
architecture_diagram_3

Here is a list of all the buttons to keyboard/mouse mappings:

Face Buttons
------------
BTN_SOUTH → Enter (Qt::Key_Return)
BTN_EAST → Escape (Qt::Key_Escape)
BTN_NORTH
BTN_WEST

Bumpers
-------
BTN_TL → Alt (Qt::Key_Alt)
BTN_TR → Tab (Qt::Key_Tab)

Trigger Buttons
---------------
ABS_Z → Mouse Left Click
ABS_RZ → Mouse Right Click

D-Pad
-----
BTN_DPAD_LEFT → Arrow Left (Qt::Key_Left)
BTN_DPAD_RIGHT → Arrow Right (Qt::Key_Right)
BTN_DPAD_UP → Arrow Up (Qt::Key_Up)
BTN_DPAD_DOWN → Arrow Down (Qt::Key_Down)

Analog Sticks
-------------
ABS_RX / ABS_RY → Pointer Motion

Center Buttons
--------------
BTN_SELECT → Show On-Screen Keyboard ( WIP )
BTN_START → Meta/Super (Qt::Key_Meta)

Prevent Stepping On Other Apps

It's essential that the plugin doesn't emulate keyboard and mouse for the gaming controller when another app is reading from it. Most likely in such cases the device is being used for something else and not being used to navigate the desktop. To achieve this the GamepadManager class creates an instance of inotify object, and adds a watch device to the fd of each game controller that’s added as a Gamepad. Whenever inotify produces a notification a function, GamepadManager::handleFdAccess, is called which increments a counter in Gamepad, Gamepad::m_usageCount by +1 if the event value is IN_OPEN or Gamepad::m_usageCount by -1 if the event value is IN_CLOSE_WRITE | IN_CLOSE_NOWRITE. The plugin will only attempt to emualte keyboard/mouse if m_usageCount is 0. This prevents emulation of keyboard and mouse when other apps have the game controller opened / in use.

// Process all inotify events in the buffer
for (char *ptr = buffer; ptr < buffer + length;) {
 struct inotify_event *event = reinterpret_cast<struct inotify_event *>(ptr);

 auto it = m_watchesToGamepads.find(event->wd);
 if (it != m_watchesToGamepads.end()) {
 Gamepad *pad = it.value();
 if (event->mask & IN_OPEN) {
 pad->countUsage(+1);
 } else if (event->mask & (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)) {
 pad->countUsage(-1);
 }
 qCDebug(KWIN_GAMEPAD) << "Device" << pad->path() << "in use by:" << pad->usageCount() << " other apps";
 }
 ptr += sizeof(struct inotify_event) + event->len;
}

Opt-In

Many of the native plugins that ship with KWin are enabled by default but for our gaming controller plugin we will disable it by default and make it an opt-in option. This will allow users to start experimenting and benefiting from the plugin without risking the possibility of breaking current game controller input on their system.

{
 "KPlugin": {
 "Category": "Input",
 "Description": "Enable KWin game controller input detection",
 "EnabledByDefault": false, <---------- Not enabled by default. 
 "License": "GPL",
 "Name": "gamepad"
 },
 "X-KDE-ServiceTypes": ["KWin/Plugin"]
}

Testing

  • Controller awareness at startup and hot-plugging: tested in development session, KWin logs show the plugin picking up controllers in both scenarios, works as expected.
  • Preventing sleep/suspend: tested in development session. Set suspend timer to 1min, repeatedly press A and B back and forth, and at 5min no suspend was initiated, works as expected.
  • USB and Bluetooth connectivity support: tested in development session, KWin logs show plugin picking up on the controllers in both scenarios, works as expected.
  • Mapping from controller to keyboard and mouse: tested in development session, all buttons are map to expected keyboard and mouse, works as expected.
  • Backoff On Grab: tested in development session. Verified mapping work, started Steam app, verify mapping no longer enabled.

Testing device: 8Bitdo Gaming Controller (USB/2.4h/Bluetooth)

What’s next from here

  • Integration into KWin Proper: Start pushing changes upstream for others to test.
  • Map to Virtual Keyboard: Allow users to navigate over and get input from a virtual keyboard. Might open the way for logging in using only game controller.
  • Test Cases: As per best practices when developing for KWin.
  • KCM integration: A GUI option for users to toggle plugin ON/OFF. Ground work for more robust, user defined, button remapping.
  • Use Config for Mapping: Using a config file to keep track of and read from all the button to keyboard/mouse button mapping.

Reference documentation:

Checkout the source code here: KWin Gamepad Plugin: https://invent.kde.org/yorisoft/kwin/-/tree/work/yorisoft/gamepad-plugin/src/plugins/gamepad

I was at Akademy 2025 last-last week where I did some preliminary research on optimizing the VM viewer’s display rendering on Karton. After some more work this past week, it’s somewhat here! I’m still finishing up the merge request, but exciting news to come!

This has been something I’ve been planning on for quite a while now and will significantly improve the experience using Karton :)

a comparison with an old video I had.

Old Rendering Pipeline

My original approach for rendering listened to display-primary-create and invalidate-display-primary SPICE signals. Everytime it received a callback, it would create a new QImage and render that to the QQuickItem (the viewer window). As you can imagine, this was very inefficient as it is basically generating new images for every single frame being rendered. It suffered a lot from screen-tearing any time there were sudden changes to the screen.

You can read more about my experiences in my SPICE client blog.

We can do better!

Rendering via OpenGL can offload a lot of these tasks to the GPU and can significantly improve performance. I had known about GL properties in SPICE for a while now, but I kept putting it off since I really didn’t want to deal with any more graphics stuff after my last attempt.

Fast forward to last-last week, I was attending my first ever KDE Akademy in Berlin and all of a sudden gained some motivation.

It was really exciting hearing talks about all the kool things happening in KDE.

gl-draw

My first order of business was getting the gl-draw signal to properly receive gl-scanouts from my SPICE connection. After setting up the callback, I found out that I had to reconfigure my VMs to properly support it.

This was easy enough as I’ve made the Karton VM installation classes a few months ago done through the libvirt domain XML format. VMs need enabling of GL and 3D acceleration through the graphics element in the XML. The socket connection to SPICE also had to be switched from TCP to UNIX, which was set to /tmp/spice-vm{uuid}.sock. As a result, previous VMs configured in Karton will no longer work as the previous rendering pipeline has been removed.

<graphics type="spice" socket="/tmp/spice-vm{uuid}.sock">
    <listen type="socket" socket="/tmp/spice-vm{uuid}.sock"/>
    <gl enable="yes"/>
</graphics>
<video>
    <model type="virtio" heads="1" primary="yes">
        <acceleration accel3d="yes"/>
    </model>
    <address type="pci" domain="0x0000" bus="0x00" slot="0x01" function="0x0"/>
</video>

An example libvirt domain XML snippet generated by Karton

Once properly configured, I was able to get SpiceGlScanout objects from my callback linked to the gl-draw signal. Now, I needed to render these scanouts onto my QQuickItem canvas.

EGL stuff

Having no background in graphics, I pretty much had no idea what I was doing by this point.

The SpiceGlScanout is a struct that looks like this:

struct SpiceGlScanout {
    gint fd;
    guint32 width;
    guint32 height;
    guint32 stride;
    guint32 format;
    gboolean y0top;
};

The width, height, stride, etc…, are all parameters that can be used to set your final rendered frame, but the important field is the fd (file descriptor) which is a “a drm DMABUF file that can be imported with eglCreateImageKHR”. I didn’t know what that was; but at least I learned I should be using the EGL library to do the processing.

I had found some forum articles (Qt forum, Arm developer forum) related to rendering OpenGL textures which used the EGL library and were quite helpful. I also looked at the SPICE GTK widget source code which gave me some ideas on the GL parameters to work with.

From these references, I saw that they pretty much followed the same pattern. Very simply put:

-> create egl image from a bunch of attributes/settings
-> generate texture from the fd 
-> bind texture to a texture type 
-> "glEGLImageTargetTexture2DOES" use this function?? still don't know what this does lol
-> destroy egl image

I originally tried setting the GL context properties manually, but there were some issues with getting it to detect my display and apparently thread syncronization. Then, I found out that Qt had a QOpenGLFunctions library which had all of the EGL functions and context properties wrapped and made my life a whole bunch easier.

OpenGL texture -> Qt

After a ton of trial and error, it looked like my EGL images were properly being created. Now I needed to render these GL textures to the QQuickItem.

How you do so is, within the inherited updatePaintNode() function, you return a QSGNode which has the information for updating that frame. Looking through the Qt documentation, QNativeTexture is a struct that allows you to store a texture ID to an OpenGL image. With that, you can create a wrapper QRhi class from the QNativeTexture with some of the generic context of your display.

Finally, you can use the createTextureFromRhiTexture() function under QQuickWindow which allows you to create a QSGTexture from that RHI for a QSGNode that can be returned by updatePaintNode(). And, we’re done! Yay!

To sum it up, here’s the framebuffer pipeline:

gl-draw signal->receive gl-scanout->import GL texture->GL texture ID->QNativeTexture->QRhi->QSGTexture->QSGNode->QQuickItem

so much smoother! yes, I was very excited.

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