Posts › KDE Mega Sprint 2026
As a relatively new member of the KDE community (June 5th 2022, per Invent), I’ve not actually met many of my fellow contributors — those few who I have seen, it has been as a video call with my fellow Techpaladin coworkers.
Three months ago, that changed, as I attended my first KDE sprint in Graz, Austria, running six days from 2026-04-06 to 2026-04-11, and overlapping with Grazer Linuxtage.
It’s been a surreal experience not only to meet everyone in-person, but to travel abroad for the first time and experience new things; in this blog I intend not only to document the things I did, but my thoughts on the whole experience.
Now, you’re probably thinking: “This happened three months ago! Why are you so late?”
Well, whilst I did finish writing the vast majority of this text some time ago now, this is a brand new and wholly original website, with lots of HTML, CSS and Javascript to get right! I also lacked a time incentive that other KDE community members had: I did not seek reimbursement from the KDE e.V., of which a requirement is to deliver a blog post.

Sprint; myself front row, second from the right; photo by Kieryn Darkwater
The Things I Did
Spectacle
I worked around a new bug in Spectacle as a result of a change in Qt 6.11: an optimisation to event delivery (for example, clicking) that is effective in most cases — don’t deliver an event to an item when the event is outside of it and the bounds of all of its children.
It introduces a new function,
QQuickItemPrivate::effectivelyClipsEventHandlingChildren() to determine
whether an item should receive events outside of its bounds, and the result is
cached.

Spectacle’s overlay mode, the offending toolbar indicated
Unfortunately, the floating annotation toolbar in Spectacle’s overlay mode dynamically (important) loads another toolbar above it when an annotation tool with options is selected, and this toolbar exists as a child outside the bounds of its parent. With this new optimisation in Qt, it became transparent to mouse interaction - it could not be clicked or hovered over, as these events would instead be delivered underneath.
A look at the code revealed there was no way to override or indicate to this new system that this optimisation should not be applied, so instead, I had to meet the conditions for the optimisation to not be applied: for there to be a child outside the bounds that accepted pointer events, and crucically, at the time that this optimisation is determined (as the result is cached). That gives the following workaround:
MouseArea {
parent: annotationsToolBar
// Be outside the parents bounds
x: -10
y: -10
width: 1
height: 1
// Meet the condition for handling pointer events
acceptedButtons: Qt.ExtraButton24
}
This fix (spectacle!524) was then shipped in 6.6.4, the subsequent release, which was itself released at the sprint the next day by David Edmundson.
It quickly resolves the issue, and a comment documented the nature of this workaround. As future work, it’d make sense for Spectacle to not have this toolbar as a child of another, but this would have been much more of an intensive change. I also think that the way Qt only determines this once (caching the result regardless of how an item’s children may change) is questionable.
What the hell is ExtraButton24 anyway? Qt calls it “The 27th non-wheel Mouse
Button.”
How did I get a screenshot of Spectacle? spectacle -i launches an instance of
Spectacle that doesn’t register itself to D-Bus, which is how Spectacle enforces
its single-instance design via KDBusService with the flag
KDBusService::Unique.
Telemetry/Metrics
At a sprint, many conversations are had (it is a good vehicle for discussion and decision-making), but most relevant to me was discussing the creation of a new system for capturing telemetry from KDE Plasma users.
It’s a sensitive topic, as we have to balance the utility of the data we gather and the privacy of users, so it’s important to stress that we absolutely intend to get this right.
A pre-existing proposal for this, discussion and a few notes from the sprint can be found here: https://invent.kde.org/plasma/plasma-desktop/-/work_items/249
Throughout the sprint, when I found myself needing something to do, I worked on building out the skeleton for a UI for this based on the existing work I’d done for Plasma’s Welcome Center.
Discover
We discussed that users can struggle to find the install button in the application page in Discover. Kirigami is designed such that actions are placed in the page’s header, in the top-right, but users generally expect that the install button appears on the page, next to the app’s icon and title.

Nate’s solution
Nate suggested a solution that makes the button non-flat (i.e. giving it a background), but this lacks precedent in any other Kirigami-based application, though it is perhaps an interesting way to differentiate a primary page action from more secondary ones.
I wanted to experiment with placing it where users expected, and came up with a quick experiment to see what that looked like. Unfortunately, with the page being scrollable, if a user scrolls to the bottom, they lose the install button and have to go back all the way to the top.
To solve this, I thought about making this section at the top of the page act like a sticky header — if you scroll down, it would stay at the top in a reduced form, and I quickly produced a mock-up of this behaviour.
After iterating on it, we arrive at the following:

My sticky header solution
A problem was that the header could be left at an uncomfortable size: if the user scrolls down just a pixel, the content switches to the reduced form, with massive margins around it. I decided it’d need to automatically scroll to settle in the right state. In QML, there wasn’t any way to determine if the user had stopped scrolling (by releasing the touchpad, releasing their finger from the screen, or letting go of the scrollbar). This made me keep my implementation simple: when the user scrolls, a timer is (re)started to ‘settle’ if necessary, by scrolling up/down such that the header has its native height in either the full or reduced form.
In order to respect the direction of the user’s scrolling when settling, I store previous scroll values, so that when settling I can sum the differences and determine the most recent direction of scrolling (minus a possible small movement on release).
All this is handled in the following code:
// Store flickable's contentY values so we can determine which way
// the user has been scrolling, so we know which way to settle
readonly property list<real> yHistory: []
onHeightChanged: {
stickyHeaderContainer.yHistory = [
root.scrollablePage.flickable.contentY,
...stickyHeaderContainer.yHistory.slice(0, 10)
]
settleTimer.restart();
}
// After a short delay, if the contentY leaves the sticky header
// height between full and sticky height, flick to one
Timer {
id: settleTimer
// veryLongDuration gives us a comfortable margin where we can
// be sure the user has stopped scrolling, but avoid taking too
// long — longDuration is too short for some particularly casual
// scrolling
interval: Kirigami.Units.veryLongDuration
// NOTE: Ideally we should only settle if the user is not
// interacting with the touchpad, touching the screen or holding
// the scrollbar, but flickable doesn't give us that info
onTriggered: {
const flickable = root.scrollablePage.flickable
if (flickable.contentY > 0
&& flickable.contentY < (stickyHeaderContainer.fullHeight
- stickyHeaderContainer.stickyHeight)
) {
// The sum of the differences between the most recent 10
// contentY values will give us a confident idea of the
// direction the user was scrolling before leaving us in
// an uncomfortable state, so we know how to correct it
// whilst respecting their intention
let differencesSum = 0;
for (let i = 0; i < stickyHeaderContainer.yHistory.length - 1; ++i) {
differencesSum += (stickyHeaderContainer.yHistory[i]
- stickyHeaderContainer.yHistory[i + 1]);
}
if (differencesSum <= 0) {
// Go to top, full header
flickable.flickTo(Qt.point(0, 0));
} else {
// Go down, sticky header
flickable.flickTo(
Qt.point(0, stickyHeaderContainer.fullHeight
- stickyHeaderContainer.stickyHeight + 1)
);
}
}
}
}
Following the sprint, I iterated on this further to separate out the sticky header code, so that the implementation in the page looks like:
ColumnLayout {
id: pageLayout
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: appInfo.internalSpacings
ApplicationPageStickyHeader {
id: stickyHeader
Layout.topMargin: -appInfo.topPadding
scrollablePage: appInfo
color: Kirigami.ColorUtils.tintWithAlpha(
Kirigami.Theme.backgroundColor,
appImageColorExtractor.dominant,
0.1
)
fullComponent: GridLayout {
[...]
}
stickyComponent: RowLayout {
[...]
}
}
// Rest of the page content, e.g. carousel, description, reviews…
[...]
}
This is much more concise, and has all the header-specific logic laid out more readably in a separate file; this will be useful for potential re-use elsewhere, perhaps in the Display Configuration settings page, where the header might want to remain but in a smaller form.
I should highlight an interesting emergent interaction (shown in the video):
because the sticky header is anchored to the top, with its height derived from
the contentY of the Flickable, and because using a touchscreen and flicking
up causes the view to overshoot, this causes the sticky header to grow then
shrink in a pleasing way. I didn’t intend for this, but it fell out of the code
and made myself and others think “Nice.” This is the sort of pleasing
interaction our applications should have.
With additional work and polish post-sprint, including further cleanup and resolving all cases where the layout might overflow, this was merged and will arrive in Plasma 6.7 (discover!1297).
I’m also considering how we can improve the install button itself in the future,
by using it to show progress (similar to
DelayButton)
as well as avoiding size changes (which can be frequent) as the button text
changes. Union will likely be important to making this possible.
Task Switching
Some time last year I began porting the Task Switcher KCM from Qt Widgets to QML, as it was somewhat low-hanging fruit towards only having QML KCMs and furthered my aim to get rid of content in Desktop Effects, with things either being integrated into other relevant settings pages, or having new ones (for instance, the recent Animations KCM).
This new KCM, titled “Task Switching”, features the Task Switcher (Alt+Tab popup) settings as well as those for the Overview effect — which is important because users won’t see it as an effect (something bonus, layered on-top), but as a core part of their desktop environment.
I had brought this work 90% of the way to completion, and at the sprint I took it a further 5% by rebasing, finishing off the new icons that would be used, and figuring out what was left before it could be shipped.
Kai Uwe Broulik, whilst not at the sprint, saw the activity and gave it a thorough review, meaning I can now bring this to 120% completion.
With Xaver’s help, I was able to find a problem Nate encountered where it turns out that config had the switcher name set to a Look and Feel ID, which is the sort of odd and obscure thing you find in older code. It looks like this never worked in the original KCM either — but because the combo box defaulted to the final index (which was the default option, “Thumbnail Grid”), it went undetected. In the new KCM, it would default to the first index, which was “None” for a disabled switcher.
I hope to finish this work soon, likely after 6.7 is branched so that it doesn’t need to be duplicated in kwin-x11 to keep both sessions consistent.
It can be found here: kwin!7601
Other
I closed a bunch of my older merge requests that were either out of date or not going to proceed further. I also picked up some smaller ones that were easy enough to update, or that I could now bring to completion.
- Unsupported applet warning message polish (plasma-workspace!6477 and libplasma!171)
- Fix ‘Energy Saving’ KCM opening with
kcmshell6and not in Info Center (kinfocenter!291) - Show actual shortcuts in Welcome Center, not hardcoded defaults (plasma-welcome!158)
- Ensure plasma-pa always prefers the actual default device (plasma-pa!393)
- Enroll Welcome Center in KCrash & Sentry (plasma-welcome!251)
- Use
LinkButtonin User Feedback settings page (plasma-workspace!6335)
Travelling
As mentioned at the start of this post, this was not only my first sprint, but my first time travelling abroad (and flying, no less). Certainly a lot of firsts for myself were happening with this trip.
Flying to Graz
I have to thank both Nate Graham and David Edmundson for helping me figure out not only how to plan my trip, but for providing solid, practicable advice (such as avoiding checked baggage). I found I could comfortably fit everything I needed with a carry-on suitcase and a backpack as my personal item.
Despite solid preparation, I didn’t sleep the night before and constantly stressed on the way to the airport about whether I had forgotten something (spoiler: I had not). I’m certain this will be less of a worry in the future, now having experience, and I’ll be able to relax more about travel.

Boarding the flight to Frankfurt
Ultimately I found it surprisingly easy to get to Graz — my only real ‘knowledge’ of flying was media-informed, surface-level clichés, but I found my way easily enough.
The first real complication happened at Birmingham. My pass said ‘Check-In Zone C’, so I naively figured that was where I had to go. The road was blocked, but it was evident from crowd movement that I just had to enter the building and walk through. Of course, I didn’t even have to go there, as I had no checked baggage, so I didn’t really have any idea of where I was going; I asked at an info desk and was directed to some lifts people were queueing for. I couldn’t see them initially, and asked again embarassed (better than wasting time confused). I just had to walk a little further than I initially thought. After taking the lift up, I reached security, or rather, the incredibly long, winding queue to it, with people occasionally pushing their way through. I did as others did, asked quick questions, and got through just fine. For some reason, after clearing security, you are obliged to walk through hallways flanked by useless tat and perfume shops on both sides. Annoying, but the signage was sufficient to find my gate easily enough, where I waited for a short time and eventually boarded.

Flying over Birmingham
It was a two-leg trip, two hours to Frankfurt, four hour layover, then two hours to Graz.
Because it was a small plane, we were required to place our carry-on luggage in the hold, which amounted to leaving it beside the plane and then also collecting it beside the plane in Frankfurt.
Flying was quite fun — I was fortunate to get a window seat for my first flight, and found myself staring out the window gormlessly much of the time, except for when we were high above the clouds with little to see.
Arriving smoothly in Frankfurt, I found it to be a much nicer and modern airport than Birmingham. There were no excessively long queues, and I was not required to walk through any shops. To my surprise, the only real scrutiny I faced was at security in Birmingham, where myself, my luggage, and my items were scanned; my media-informed impression created the expection that intensive security would exist at all stages. As well, the European Entry/Exit System (EES) did not seem to be fully implemented, as whilst I saw terminals for recording biometrics and scanning my passport, I was not required to use them. Entry into the Schengen area amounted only to a few questions about my trip and a stamp on my passport.

A very long travelator at Frankfurt Airport
From Frankfurt, I made my way to Graz on my second flight, which went similarly, with my carry-on being surrendered at the aircraft and placed into the hold. I was a little confused that it was collected at the carousel rather than at the aircraft (asking about it quickly clarified the matter), but this was not even slightly a concern given Graz airport’s smaller size.
I found my fellow Techpaladin coworker Marco, conveniently identified by KDE garb, and we went together to the hotel directly in a taxi. From there, I was able to meet even more of my fellow KDE contributors, make introductions, and participate in the sprint. Suffice it to say I had a very good and productive time.
Graz
Graz is quite a nice place. Unlike in England, the roads were not marked by frequent potholes, and public transport was readily available and easily accessed. You don’t have to wait for others to disembark the bus through the only doors, step on, and sort out a ticket, wait for it to print, and sit down (and then wait for others). You just stepped on through one of several double-wide doors, and work out your ticket at the kiosk machine. Much more efficient.
There were other differences too, of course. People drove on the right (which took a quick mental recalibration), and when going out for a meal or drinks, you’d pay at the end rather than straight away. Apparently this is common in many places. Also efficient.
The biggest difference was that cider (actual alcoholic cider, not American apple juice) is not readily available. Maybe it was just bad luck, but Austrians apparently quite like their beers and wines, but not anything that actually tastes nice. Some quick research tells me that whilst cider is commonplace in the UK, it is nowhere near as common in mainland Europe. Still, it was occasionally found, and Graz apparently had their own which was delicious.

KDE’s stall at Grazer Linuxtage
I do regret not taking time to see more of Graz than the limited area walked during the sprint or evenings, to see the iconic clocktower or to find a tourist shop that had fridge magnets. I’ll have to amend this when returning to Graz later this year for Akademy.
Near the end of the sprint, Grazer Linuxtage was happening. I was aware we had a stall there, and I picked up a lovely laser-etched KDE logo for my key ring and a sticker for my laptop. I did consider buying myself a Konqi, but I don’t really have a good spot for it and I thought it best to leave it for others visting the stall. I was also able to briefly try Plasma Mobile running on real mobile hardware, and the look and experience seemed to be quite good.
I also attended Albert’s talk at the conference along with a bunch of other KDE contributors, which was a nice and informative look back on 30 years of KDE and the Linux desktop. A recording of this talk can be found here.
Flying Home
Flying back was also a simple affair; I felt I now knew what I was doing. I used Uber to get a taxi to the airport, which my first driver made a sour experience by trying to get me to cancel (which would have awarded him a cancellation fee at my expense). I was able to request another driver, who did actually arrive and took me to the airport.

My flight preparing to leave Graz
There, myself and my luggage swiftly passed through security, though I was for some reason swabbed for explosives residue. I met with Marco and Kristen whom had the same flight, and began to write this blog (and then later lost it by not making a copy before wiping my laptop to perform a BIOS update via Windows in the hope of resolving some issues. No matter, as it was rather brief and not as good as this.)
I boarded my flight to Munich, with my carry-on above me this time, rather than in the hold. I was fortunate to have another window seat, which entertained me for the flight, but unfortunate in that the person sat next to me was intermittently coughing; I attribute myself being ill for most of the following week to this. I’ll likely wear a mask when flying in the future, as it’d certainly suck if this were my experience on the starting leg of the trip and it knocked me out for the duration of an event.

My gate at Munich Airport
An hour and a half later I landed in Munich, which I was pleased to find was even more modern and even nicer than Frankfurt. Very sleek, clean and even the typography on the signage was satisfying. Every airport should aspire to be like this.
I quickly found my gate, waited and boarded my final flight straight back to Birmingham. My carry-on was again above me this time, and unlike the previous flights, which had two seats to each side, this was a larger plane with three seats a side. Unfortunately, luck of the draw placed me in the middle seat. With children behind me. For two hours and thirty minutes.

The drop-off area at Birmingham Airport
At the end of it all, I was back right where I started in Birmingham. It was a bit of a somber mood: the airport felt cheap and crowded, and I was back in the land of left-handing driving, grim weather and potholes. Probably, I was more than a bit frustrated due to flying packed between two strangers, and it certainly didn’t help to have come from the best airport I’d seen yet. Still, I was home (more or less, just over an hour’s drive away), and I’d had an exceptional week.
Closing
I am very much looking forward to returning to Graz for Akademy 2026 later this year. Having had time to familiarise myself with Graz, travel and having introduced myself to some of my fellow KDE contributors, I’m quite confident that it’s going to be an even better experience.
Certainly, I shall have to ensure that I take the time to explore more of Graz on this next trip.
Further Reading
Others have published blog posts about their experience at the sprint:
- https://mxdarkwater.com/2026/04/10/kde-in-graz/
- https://tsdgeos.blogspot.com/2026/04/a-week-in-graz-kde-megasprint-and.html
- https://merritt.codes/blog/2026/04/15/2026/_kde_mega_sprint
- https://www.volkerkrause.eu/2026/04/18/kde-sprint-linuxtage-graz-2026.html
- https://nmariusp.blogspot.com/2026/04/kde-mega-sprint-2026-graz.html
- https://blogs.kde.org/2026/05/03/gestures-in-graz-and-beyond/