Skip to content

Tuesday, 17 February 2026

Plasma Setup, the new wizard that guides users through the initial configuration of KDE Plasma, is having its first release as part of the Plasma 6.6 release!

With Plasma Setup, the technical steps of operating system installation and disk partitioning can be handled separately from user-facing steps like setting up an account, connecting to a network, and so on. This facilitates important use cases such as:

  • Companies shipping Plasma pre-installed on devices
  • Businesses or charity organizations refurbishing computers with Plasma to give them new life
  • Giving away or selling a computer with Plasma on it, without giving the new owner access to the previous owner’s data

This has been several months in the making, as it has been my primary focus ever since I was hired by KDE e.V. last year. The project has seen a ton of work, and we've collaborated with distros and other stakeholders to ensure it meets the needs of the community.

I am very excited to see Plasma Setup finally in the hands of users, and how it will make KDE (and Linux/FOSS in general) more accessible to a wider audience. This is a key piece that was needed in order for Plasma to be more viable and accessible to a whole class of users (non-technical end-users, businesses, governments, etc.).

There are still plenty of improvements that can be made, and contributions are very welcome! If you are interested in contributing, please check out the project on KDE's GitLab: https://invent.kde.org/plasma/plasma-setup

Sunday, 15 February 2026

Tellico 4.2 is available, with some improvements and bug fixes. This release now requires Qt6 (> 6.5) as well as KDE Frameworks 6. One notable behavior change is that when images are removed from the collection, the image files themselves are also removed from the collection data folder.

Users have provided substantial feedback in a number of areas to the mailing list recently, which is tremendously appreciated. I’m always glad to hear how Tellico is useful and how it can be better. Back up those data files!

Improvements:

Bug Fixes:

  • Fixed bug with XML generation for user-locale (Bug 512581).
A new version (3.4) of Bouncy Ball has just been released on the KDE Store. You can update through Discover, or by heading over to the store: https://store.kde.org/p/2344070 Previous posts:Bouncy Ball will always bounce backThis week in Bouncy Ball – new features land I’m happy to share that this version now includes support for custom...... Continue Reading →
Game AI engines, particularly those using tree search algorithms like alpha-beta pruning and MTD(f), are computationally intensive. As modern devices from desktops to mobile phones feature multi-core processors, parallelizing these algorithms has become essential for creating stronger AI opponents w…

Saturday, 14 February 2026

So, while working with caching and scrapping, I understood the difference between immutable and mutable objects/datatypes very clearly. I had a scenario, where I am webscraping an API, the code looks like this.

from aiocache import cached

@cached(ttl=7200)
async def get_forecast(station_id: str) -> list[dict]:
 data: dict = await scrape_weather(station_id)
 # doing some operation
 return forecasts

and then using this utility tool in the endpoint.

async def get_forecast_by_city(
 param: Annotated[StationIDQuery, Query()],
) -> list[UpcomingForecast]:
 forecasts_dict: list[dict] = await get_forecast(param.station_id)
 forecasts_dict.reversed()

 forecasts: deque[UpcomingForecast] = deque([])
 for forecast in forecasts_dict:
 date_delta: int = (
 date.fromisoformat(forecast["forecast_date"]) - date.today()
 ).days
 if date_delta <= 0:
 break
 forecasts.appendleft(UpcomingForecast.model_validate(forecast))

 return list(forecasts)

But, here is the gotcha, something I was doing inherently wrong. Lists in python are mutable objects. So, reversing the list modifies the list in place, without creating a new reference of the list. My initial approach was to do this

I recently had the opportunity to sit down with my mentor, Schimon Jehudah, for an intensive technical session.

Hey everyone!

I am Siddharth Chopra, a second year engineering student at the Indian Institute of Technology, Roorkee. I'm really excited to be working on Marknote as a part of the Season of KDE program this year, under the mentorship of Carl Schawn.

Marknote, as it is aptly named, is KDE's own markdown based note taking app. The aim of my project is to improve Marknote by adding the much requested source mode, alongside other enhancements.

Progress so far

3 weeks into the project, I have been successful in adding a working source mode functionality to the editor. When source mode is activated, the contents of the source markdown file are allowed to be edited directly, instead of showing the rendered markdown. This is incredibly useful, in cases where the user needs manual control over the contents of the note, or in case there is some glitch in the rendering (which unfortunately still happens often).

Demo Video

Technical Roadmap & Challenges

The main editor of Marknote comes from the file EditPage.qml. As part of my initial approach, I added a global property here to check if source mode was enabled, and then conditionally changed components of this editor. Although this worked, but it brought along some of its own issues. First of all it made the code unnecessarily complex. It also meant that components like the formatting bar now needed to be repurposed to work with source mode, which is a challenge in itself.

So, my mentor suggested to move the raw editor into a new file, to keep the code maintainable. This led to the original EditPage being split into RichEditPage and RawEditPage. Similarly, the respective backends were also split in two, as the needs of both the editors are significantly distinct.

Additionally, I had to consider specially the source mode for images. Because when loaded, image URL's are not kept intact, instead they are replaced with a hash, that maps to the image in memory. Also, the editor internally uses html for rendering images, which I also had to convert back to markdown for source mode.

And someone who is not a designer by any means, deciding the form and placement of the mode toggle button was in itself a mini lesson in UI design ;) Initially I went with a toggle switch. But when I shared that for feedback, I learnt that a checkable button is the ideal UI element here.

Future Plans

My proposal mentions features apart from the source mode, which I plan to complete. While working on the current feature, I noticed multiple bugs in the app, which I intend to fix as well.

Overall experience

It has been a great experience working with the KDE community, and really exciting to be able to contribute to an app that so many users around the world use every day! I would also like to express my gratitude towards my mentor, for being there for whatever issue I faced!

Friday, 13 February 2026

This release brings improvements to generators, better build system integration and several bugfixes.

As always, big thanks to everyone who reported issues and contributed to QCoro. Your help is much appreciated!

Directly Awaiting Qt Types in AsyncGenerator Coroutines

The biggest improvement in this release is that QCoro::AsyncGenerator coroutines now support directly co_awaiting Qt types without the qCoro() wrapper, just like QCoro::Task coroutines already do (#292).

Previously, if you wanted to await a QNetworkReply inside an AsyncGenerator, you had to wrap it with qCoro():

QCoro::AsyncGenerator<QByteArray> fetchPages(QNetworkAccessManager &nam, QStringList urls) {
 for (const auto &url : urls) {
 auto *reply = co_await qCoro(nam.get(QNetworkRequest{QUrl{url}}));
 co_yield reply->readAll();
 }
}

Starting with QCoro 0.13.0, you can co_await directly, just like in QCoro::Task:

QCoro::AsyncGenerator<QByteArray> fetchPages(QNetworkAccessManager &nam, QStringList urls) {
 for (const auto &url : urls) {
 auto *reply = co_await nam.get(QNetworkRequest{QUrl{url}});
 co_yield reply->readAll();
 }
}

Other Features and Changes

  • Generator’s .end() method is now const (and constexpr), so it can be called on const generator objects (#294).
  • GeneratorIterator can now be constructed in an invalid state, allowing lazy initialization of iterators (#318).
  • qcoro.h now only includes QtNetwork and QtDBus headers when those features are actually enabled, resulting in cleaner builds when optional modules are disabled (#280).

Bugfixes

  • Fixed memory leak in QFuture coro wrapper when a task is destroyed while awaiting on a QFuture (#312, Daniel Vr√°til)
  • Fixed include paths when using QCoro with CMake’s FetchContent (#282, Daniel Vr√°til; #310, Nicolas Fella)
  • Fixed QCoroNetworkReply test on Qt 6.10 (#305, Daniel Vr√°til)

Full changelog

See changelog on Github

Support

If you enjoy using QCoro, consider supporting its development on GitHub Sponsors or buy me a coffee on Ko-fi (after all, more coffee means more code, right?).

Going into Week 3, my next task was clear. I had successfully stopped the Lokalize menubar from jumping around, but it left me with top-level menus that were clickable but completely empty depending on which tab you were using. My goal was to figure out how to dynamically grey out those useless menu headers, like the "Sync" menu in the Project Overview, so users wouldn't be tricked into clicking them.

I started by checking the Qt documentation and a very helpful discussion on the Qt forums to see how to properly handle this. The forum thread highlighted a crucial distinction in the Qt API: using setVisible(false) completely hides a menu, while setEnabled(false) keeps it visible but greys it out. This was an important detail. If i had used setVisible(), the menus would have vanished completely, bringing back the jumping ui bug I had just fixed in Week 1. I needed to use setEnabled() to keep the menu structure frozen in place while making the empty headers unclickable.

To implement this, I hooked directly into the application's tab-switching logic. I wrote a new function that gets triggered every single time a user changes tabs. It grabs the main menubar and loops through every top-level action. For each menu it finds, it checks if the menu has any currently visible actions. If the answer is no, it disables that specific menu header using action->setEnabled(false), turning the menu grey so the user knows it cannot be clicked.

void LokalizeMainWindow::updateMenuAvailability()
{
    QMenuBar *bar = menuBar();
    if (!bar)
        return;

    // Refresh top-level menu state based on currently available actions.
    for (QAction *action : bar->actions()) {
        QMenu *menu = action->menu();
        if (!menu)
            continue;

        // Disable top-level menu when it has no visible actions.
        action->setEnabled(menuHasVisibleAction(menu));
    }
}

bool LokalizeMainWindow::menuHasVisibleAction(const QMenu *menu) const
{
    for (QAction *action : menu->actions()) {
        if (action->isSeparator() || !action->isVisible())
            continue;

        if (QMenu *subMenu = action->menu()) {
            // Check submenu actions too.
            if (menuHasVisibleAction(subMenu))
                return true;
            continue;
        }
        return true;
    }
    return false;
}

The tricky part was writing the helper function to accurately figure out if a menu was actually "empty." It wasn't enough to just check if the menu contained items. Inside the helper, I loop through the menu's actions. If an item is just a ui line (a separator) or is already hidden, the code skips it. I also had to account for nested submenus, so if the action is a submenu, the function calls itself recursively to check inside it. The implementation is completely generic-meaning if anyone adds new menus or actions to Lokalize in the future, this code will handle them automatically.

After the fix: In the Translation Memory tab, the Edit, Go, and Sync menus dynamically grey out. This adapts instantly to whichever tab is active (for example, switching to the Project Overview will only grey out the Sync menu)
After the fix: In the Translation Memory tab, the Edit, Go, and Sync menus dynamically grey out. This adapts instantly to whichever tab is active (for example, switching to the Project Overview will only grey out the Sync menu)

I opened the merge request, and i'm happy to say it was quickly merged into master! The Lokalize menus now update smoothly based on the context of the active tab. They stay exactly where they belong, and empty menus disable themselves as expected. With this menubar issue resolved:)

Thursday, 12 February 2026

As explained in one of my previous blog posts where I revamped the unresponsive window dialog, KWin isn’t really designed to show regular desktop windows of its own. It instead relies on helper programs to display messages. In case of the “no border” hint, it just launched kdialog, a small utility for displaying various message boxes from shell scripts. This however came with a couple of caveats that have all been addressed now:

KWrite (simple text editor) window without window border. Ontop a dialog “You have selected to show a window without its border. Without the border, you will not be able to enable the border again using the mouse: use the window menu instead, using the Alt+F3 keyboard shortcut”, buttons “OK” and “Restore Border”
Setting no border now lets you restore it in case you really don’t have a keyboard

First of all, the dialog wasn’t attached to the window that provoked it. When the window was closed, minimized, or its border manually restored, it remained until manually dismissed. Secondly, it said “KDialog” and used a generic window icon (that would have been an easy fix of course). Further, the user might not even have kdialog installed which actually was the case on KDE Linux until very recently. Ultimately and most importantly, it told you that you were screwed if you didn’t have a keyboard but didn’t offer and help if you really went without one. I therefore added an option to restore the border right from the dialog. Should you have a dedicated global shortcut configured for this action, it will also be mentioned in the dialog.

The dialog when manually setting a window full-screen has similarly been overhauled, including an undo option. While at it, I removed the last remaining code calling kdialog, too: the “Alt+tab switcher is broken” message. It is now a proper KNotification. Something you should never see, of course.

Another dialog that I gave some attention was the prompt when copying a file would overwrite its destination. If you have Kompare installed and copy a plain text file (that includes scripts, source code, and so on), the dialog displays a “Compare Files” button. It already guesstimated whether the files are similar but now you can actually see it for yourself.

Dialog prompt asking “Would you like to overwrite the destination?”, Source (from a ZIP file) and Destination (in Downloads folder) both show a preview image and size and modified time.
Extracting a file from Ark now displays information about the source, too

KIO’s PreviewJob that asynchronously generates a file preview now provides the result of its internal stat call, too. This means that once you receive the preview, you also get the file’s properties, such as size, modified time, and file type basically for free. The rename dialog then displays this information in case it wasn’t provided by the application already. Dolphin now also makes use of this information while browsing a folder which should improve responsiveness when browsing NFS and similarly mounted network shares. At least when previews are enabled, it no longer determines file types synchronously in most scenarios.

Since the rename dialog is able to fetch file information on demand, Ark, KDE’s archiver tool, rewrites the source URL it displays in the dialog to a zip:/ URL (or tar:/ or whatever supported archive type). This way the dialog can display a preview transparently through the Archive KIO worker which also gained the ability to determine a file’s type from its contents. In case you didn’t know, you can configure Dolphin to open archives like regular folders.

Finally, most labels in KIO that show the mtime/ctime/atime no longer include the time zone unless it is different from the system time zone. Showing “Central European Standard Time” in full after every date was a bit silly. Unfortunately, QLocale isn’t very flexible and only knows “short” (19:00) or “long” (19:00:00 Central European Standard Time) formats. You can’t explicitly tell it to generate a time string with seconds, or with 12/24 hour clock, or a date with weekday but no year, and unfortunately the “long” time format includes the full time zone name.