Skip to content

Monday, 9 February 2026

The second maintenance release of the 25.12 series is with the usual batch of stability fixes and workflow improvements. Highlights of this release include fixes to various monitor issues and refactoring of the monitor dragging mechanism. See the changelog below for more details.

Kdenlive needs your support

Our small team has been working for years to build an intuitive open source video editor that does not track you, does not use your data, and respects your privacy. However, to ensure a proper development requires resources, so please consider a donation if you enjoy using Kdenlive - even small amounts can make a big difference.

For the full changelog continue reading on kdenlive.org.

Sunday, 8 February 2026

Minimalism came in like a wrecking ball somewhere around 2013. It delivered a terminal diagnosis to all but a few prevailing designs at the time. One of them, called Oxygen, had reigned supreme in KDE Plasma. As with many others, its demise was inevitable. Anyone aspiring to demonstrate that they were building something new and...... Continue Reading →

Saturday, 7 February 2026

Transitous is a project that runs a public transport routing service that aspires to work wold-wide. The biggest leaps forward in coverage happened in the beginning, when it was just a matter of finding the right urls to download the schedules from. Most operators provide them in the GTFS format, which is also used in Google Transit and a few other apps.

However, the number of readily available GTFS schedules (so called feeds) that we are not using yet is starting to become quite small. As evident when comparing with Google Transit, there are still a number of feeds that are only privately shared with Google. This is not great from a standpoint of preventing monopolies and also a major problem for free and open-source projects which don’t have the resources to discuss with each operator individually or to even buy access to the data from them. Beyond that case, there is still a suprisingly large number of places in the world that do not publish any schedules in a standardized format, and that is something that we can fix.

Source data comes in many shapes and forms, but the ingredients we’ll definitely need are:

  • the lines
  • stop times
  • stop locations
  • and service dates

Sometimes you can find a provider specific API that returns the needed information, or there is open data in a non-standard format. In the worst case, it might be necessary to scrape data out of the HTML of the website.

Some examples:

a stop time from the API of ŽPCG (Railway in Montenegro):

Example 1:

{
    "ArrivalTime" : "15:49:16",
    "DepartureTime" : "15:51:16",
    "stop" : {
        "Latitude" : 42.511829,
        "Longitude" : 19.203468,
        "Name_en" : "Spuž",
        "external_country_id" : 62,
        "external_stop_id" : 31111,
        "local" : 1,
    }
}

It is immediately visible that we get the stop times, in for some reason extreme precision. We also get coordinates for the location, which makes conversion to GTFS much easier. Unfortunately the coordinates from this dataset are not exactly great, and can easily be off by multiple kilometers, but they nevertheless provide a rough estimate that we can improve on by matching them to OpenStreetMap.

The railway-data enthusiasts will also notice that we get a UIC country code and a stop code, which we can concatinate to get a full UIC stop identifier. We can make use of that for OSM matching later on.

Example 2:

<ElementTrasa Ajustari="0" CodStaDest="27888" CodStaOrigine="23428" DenStaDestinatie="Ram. Budieni"
    DenStaOrigine="Târgu Jiu" Km="1207" Lungime="250" OraP="45180" OraS="45300" Rci="R" Rco="R" Restrictie="0"
    Secventa="1" StationareSecunde="0" TipOprire="N" Tonaj="500" VitezaLivret="80"/>

This example is from open data for railways in Romania. Unfortunately this one does not give us coordinates, and the fact that the fields are in abbreviated Romanian doesn’t make it too easy to understand for someone like me who does not speak any vaguely related language. However looking at the numbers, we can figure out that OraP and OraS are seconds and provide the departure and arrival times. However here, the data does not model the times at stops, but the transitions between the stops, so some more reshuffling is necessary.

Example 3:

{
    "ArrivalTimes" : "12:35, 13:37, 14:02, 14:21, 14:52, 14:27, 15:04, 15:50, 16:14, 16:39, 17:08, 17:36, 18:55, 19:03, 19:12, 20:05, 20:33, 21:12, 21:47",
    "Classes" : "2",
    "DepartureTimes" : "12:35, 13:38, 14:03, 14:22, 15:12, 14:42, 15:29, 15:51, 16:15, 16:40, 17:33, 17:37, 18:57, 19:08, 19:13, 20:06, 20:34, 21:13, 21:47",
    "Route" : "Vilnius-Krokuva",
    "RouteStops" : "Vilnius, Kaunas, Kazlų Rūda, Marijampolė, Mockava, Trakiškė/Trakiszki, Suvalkai/Suwałki, Augustavas/Augustów, Dambrava/Dąbrowa Białostocka, Sokulka/Sokółka, Balstogė/Białystok, Balstogė (Žaliakalnio stotis) / Białystok Zielone Wzgórza, Varšuva (Rytinė stotis)/ Warszawa Wschodnia, Varšuva (Centrinė stotis)/ Warszawa Centralna, Varšuva (Vakarinė stotis)/ Warszawa Zachodnia, Opočnas (Pietinė stotis)/Opoczno Południe, Vloščova (Šiaurinė stotis)/Włoszczowa Północ, Mechuvas/Miechów, Krokuva (Pagrindinė stotis)/ Kraków Główny",
    "RunWeekdays" : "1,2,3,4,5,6,7",
    "Spaces" : "WHEELCHAIR, BICYCLE",
    "TrainNumber" : "33/141"
}

This example is from the, to my knowledge, only source of semi-machine-readable information on railway timetables in Lithuania. While json is straightforward to parse, this one for some reason does not use json arrays, but comma-separated lists. We can still work with this of course, until a stop appears whose name contains a comma. Oh well. Once again, no coordinates are provided. While it is tempting to think this should be easier to convert than xml in Romanian, this format has some more hidden fun. If you have been to the area the line from the example operates in, you might already have noticed that the time zone changes between Lithuania and Poland. Unfortunately, there is no notice of that in the data, just a randomly backwards-jumping arrival and departure time.

To work with this, we first need to match the stop names to coordinates, then figure out the time zones from that, and then convert the stop times.

Matching stations to locations

OpenStreetMap is the obvious choice for this. It is fairly easy to query the station locations, but matching the strings to the node in OSM is not trivial. There are often variations in spelling, particularly if the data covers neighbouring countries with different languages. The data may also have latinized names, while the country usually uses a different script and so on.

Since this problem comes up repeatedly, I am slowly improving my rust library (gtfs-generator) for this, so it can hopefully handle most of these cases automatically at some point. It aims to be very customizable, so the matching criteria needs to be supplied by the library user. The following example matches a stop if it has a matching uic_ref tag, which is a strong identifier. If no node has such a matching tag, all nodes in the radius of 20km are considered if their name is either a direct match, an abbreviation of the other spelling, similar enough or has matching words. The matching radius can be overridden in each query, so if nothing is known yet, the first guess can be the middle of the country with a large enough radius. As soon as one station is known, the ones appearing on the same route must be fairly close. The matching quality strongly depends on making a good guess of the distance from the previous stop, as it greatly reduces the risk of similarly named stations being mismatched.

Since OpenStreetMap provides different multi-lingual name tags, the order that these should be considered in needs to be set as well.

The code for matching will look something like this:

let mut matcher = osm::StationMatcherBuilder::new()
    .match_on(osm::MatchRule::FirstMatch(vec![
        osm::MatchRule::CustomTag {
            name: "uic_ref".to_string(),
        },
        osm::MatchRule::Both(
            Box::from(osm::MatchRule::Position {
                max_distance_m_default: 20000,
            }),
            Box::from(osm::MatchRule::FirstMatch(vec![
                osm::MatchRule::Name,
                osm::MatchRule::NameAbbreviation,
                osm::MatchRule::NameSimilar {
                    min_similarity: 0.8,
                },
                osm::MatchRule::NameSubstring,
            ])),
        ),
    ]))
    .name_tag_precedence(
        [
            "name",
            "name:ro",
            "short_name",
            "name:en",
            "alt_name",
            "alt_name:ro",
            "int_name",
        ]
        .into_iter()
        .map(ToString::to_string)
        .collect(),
    )
    .transliteration_lang(TransliterationLanguage::Bg)
    .download_stations(&["RO", "HU", "BG", "MD"])
    .unwrap();
    
    // Parse input

    let station = matcher.find_matching_station(&osm::StationFacts {
        name: Some(name),
        pos: Some(previous_coordinates.unwrap_or((46.13, 24.81))), // If we know nothing yet, bias to the middle of Romania, so we at least don't end up in the wrong country
        max_distance_m: match (previous_coordinates, previous_time, atime) {
            // We have previous coordinates, but nothing for this station. Base limit on max reachable distance at reasonable speed
            (Some(_prev_coords), Some(departure), Some(arrival)) => {
                let travel_seconds = arrival - departure;
                Some(travel_seconds * 70) // m/s
            }
            // No previous location known
            _ => Some(800000),
        },
        values: HashMap::from_iter([("uic_ref".to_string(), uic_ref.clone())]),
    });

For now, until I’m somewhat certain about the API, you’ll need to use the git repository directly to use the OSM matching feature.

Finally writing the GTFS file

After all the ingredients are collected, the actual GTFS conversion should be fairly easy. We now need to sort the data we collected into the main categories of objects represented by GTFS, routes, trips, stops, and stop_times.

Every trip needs to have a corresponding route. The exact distinction between different routes depends on the specific transit system. In the simplest case, if multiple buses operate with the same line number on the same day, they would belong to the same route. Each time the bus operates per day, a new GTFS trip starts.

If the system does not have the concept of routes, every trip simply is its own route. This is for example what Deutsche Bahn does for ICE trains, where each journey has it’s own train number.

The code for building the GTFS data looks somewhat like this:

    let mut gtfs = GtfsGenerator::new();

    // parse input

    gtfs.add_stop(gtfs_structures::Stop {
        id: stop_time.station_code.to_string(),
        name: Some(stop_time.station_name.to_string()),
        latitude: coordinates.as_ref().map(|(lat, _)| *lat),
        longitude: coordinates.as_ref().map(|(_, lon)| *lon),
        ..Default::default()
    })
    .unwrap();

    gtfs.add_stop_time(gtfs_structures::RawStopTime {
        trip_id: trip_id.clone(),
        arrival_time: Some(atime.unwrap_or(dtime)),
        departure_time: Some(dtime),
        stop_id: stop_time.station_code.to_string(),
        stop_sequence: stop_time.sequence,
        ..Default::default()
    })
    .unwrap();
    
    gtfs.write_to("out.gtfs.zip").unwrap();

Validating the result

A new GTFS feed is rarely perfect on the first try. I recommend running it through gtfsclean first. After all obvious issues are fixed (missing fields, broken references), you can use the validator of the French government and the canonical GTFS validator. It is worth using both, as they check for slightly different issues.

Once all critical errors reported by the validators are fixed, you can finally test the result in MOTIS. You can get a precompiled static binary from GitHub Releases. Afterwards create a minimal config file using ./motis config out.gtfs.zip. The API on it’s own is not too useful for testing, so add

server:
  web_folder: /path/to/ui/directory

to the top of the file to make the web interface available.

Make sure to also enable the geocoding option, so you can search for stops.

Now all that’s needed is loading the data and starting the server:

./motis import
./motis server

You should now be able to search for your stops and find routes:

MOTIS showing a connection between Vilnius and Riga

Publishing

Once it is ready, the GTFS feed needs to be uploaded to a location that provides a stable url even if the feed is updated. The webserver should also support the Last-Modified-header, so the feed can be downloaded only when needed. A simple webserver serving a directory like nginx or apache works well here, but something like Nextcloud works equally well if you already have access to an instance of it.

Since the converted dataset needs to be regularly updated, I recommend setting up a CI pipeline for that purpose. The free CI offered by gitlab.com and GitHub is usually good enough for that. I recommend setting up pfaedle in the pipeline, to automatically add the exact path the vehicles take based on routing on OpenStreetMap data.

Once you have a URL, you can add it to Transitous and places where other developers can find it, like the Mobility Database

If you are interested in some examples of datasets generated this way, check out the Mobility Database entries for LTG Link and ŽPCG. You can find a list with some examples of feeds I generate here, including the generator source code based on the gtfs-generator crate.

You can always ask in the Transitous Matrix channel in case you hit any roadblocks with your own GTFS-converter projects.

This is a sequel to my first blog where I’m working on fixing a part of docs.kde.org: PDF generation. The documentation website generation pipeline currently depends on docs-kde-org, and this week I was focused on decoupling it.

Moving away from docs-kde-org

I began by copying the script that builds the PDFs in docs-kde-org to ci-utilities as a temporary step towards independence.

I then dropped the bundled dblatex for the one present in the upstream repos, wired up the new paths and hit build. To no one’s surprise, it failed immediately. The culprit here was the custom made kdestyle latex style. As a temporary measure, I excluded it to get PDFs to generate again, however several (new) locales broke.

So the pipeline now worked, albeit, partially.

(Commit 17bbe090) (Commit 3e8f8dc9)

Trying XeTeX

Most non-ASCII languages (Chinese, Japanese, Korean, Arabic, etc) are not supported for PDF generation because of wonky unicode support in pdfTeX (the existing backend driver that converts LaTeX code to a PDF)

So I switched the backend to XeTeX to improve the font and language handling. Now I’m greeted with a new failure :D

1
xltxtra.sty not found

xltxtra is a package used by dblatex to process LaTeX code with XeTeX. Installing texlive-xltxtra fixed this, and almost everything built (except Turkish)

(Commit 7ede0f94)

The deprecated package rabbit hole

While installing xltxtra, I noticed that it was deprecated and mostly kept for compatibility. So I tried replacing it with modern alternatives (fontspec, realscripts, metalogo).

Back to xltxtra.sty not found

At the point I looked at the upstream dblatex code and found references to xltxtra in a Latex style file and in the XSL templates responsible for generating the latex.

I experimented with local overrides, but the dependency was introduced during the XSLT stage so it isn’t trivial to swap out.

Clearer mental model

A very good outcome from the experiments is that I now understand the pipeline much better

1
DocBook XML -> XSLT Engine -> LaTeX Source -> XeLaTex -> PDF

XSL files are templates for the XSLT engine to convert the docbooks to latex and the problem is probably in one of these templates

Decision with mentor

After discussion, Johnny advised me to

  • Use texlive-xltxtra for now
  • Open an upstream issue
  • And that fixing dblatex itself is outside our scope

So I went ahead and filed the issue https://sourceforge.net/p/dblatex/bugs/134/. Now we keep moving forward while also documenting the technical debt.

Locales experiment

I temporarily re-enabled previously excluded locales (CJK etc) to see what happens. Unfortunately most of them didn’t generate silently. I’m still unsure about this part and the behavior might differ in other projects, so I’ll revisit this later.

(Commit 04b698a5)

kdestyle strikes back

Now I fed the absolute path of kdestyle to dblatex (to maintain PDF output parity) which introduced a new set of errors

  • Option clash for graphicx
  • conflicting driver options in hyperref (dvips, pdftex)

These seem to originate from the style file and is the next major thing to resolve.

(Commit 7ede0f94)

Improved Git skills

I also had a self inflicted problem this week. My feature branch diverged from master and I merged instead of rebasing. As a result the history got messed up. I fixed it by:

  • dropping the merge commit
  • rebasing properly
  • force pushing the cleaned branch

Lessons learnt. Working with open source projects has really made git less intimidating.

Where things stand

Right now

  • we can build with XeTeX
  • we rely on texlive-xltxtra
  • most locales work (except Turkish of course)
  • kdestyle introduces some problems

Also here’s a before vs after of the generated PDFs (the end goal would be to make both of them look identical)

Honestly I don’t feel like I got much accomplished but the understanding gained should make future changes much faster (and safer :D)

Dev logs log 1, log 2, log 3, log 4

Edit (09-02-2026): Added links to relevant commits based on feedback received

In my first week of Season of KDE, I managed to stop the Lokalize menubar from jumping around by enforcing a strict "Global Skeleton" in the XML files. We gave tabs empty menus just to hold space and keep the layout from shifting. It solved the jumping bug perfectly, but it introduced a new, slightly awkward UX problem.

Because those empty menus were technically just sitting there to hold space, they were still clickable. If you were in the Project Overview tab and clicked "Sync," nothing happened. No dropdown appeared, it was just a clickable menu that did nothing. It didn't crash the app, but it felt unfinished. My mentor, Finley, pointed this out and showed me how Kate handles this by greying out unusable actions. To see if we could replicate that, i built a massive "Complete Menu Action Comparison" matrix to map every action across every tab.

The matrix proved we couldn't just copy Kate. Lokalize menus change drastically depending on context-the Edit menu has 20 actions in the Editor, but only one (Find) in the Project Overview. Showing 19 greyed-out, irrelevant actions would just clutter the ui. Instead, Finley suggested a better approach: what if we grey out the menu headers themselves right on the menubar? Figuring out how to code that became my primary goal for week 3.

While analyzing the menu architecture, we also discussed whether to move all menu definitions into one centralized file, dropping the individual KXMLGUIClient .ui.rc files. I analyzed how other major KDE projects like Kate, Okular, Dolphin, and Ark to see how they handled it.

I found out that every major KDE app keeps a distributed model and merges dynamically. Centralizing Lokalize would force us into manually managing visibility and shortcuts, creating a fragile refactor. I shared this with Finley, and we agreed to stick to the distributed approach.

Also when i was working with .rc files, I found a graveyard of "ghost actions"-XML entries with zero implementation in the C++ backend. I drafted an email to the KDE translators' mailing list asking for direct feedback on which actions they actually use in their workflows so i can decide which actions i need to implement or remove.

Friday, 6 February 2026

KDE Eco is an ongoing initiative taken by the KDE community to support the development and adoption of sustainable Free & Open Source Software worldwide and I am happy to be able to contribute to this mission as part of Season of KDE 2026.

As part of this initiative, KDE aims to measure the energy consumption of its software and eco-certify applications with the Blue Angel ecolabel. To make this possible, KEcoLab a dedicated lab based in Berlin provides remote access to energy-measurement hardware via a GitLab CI/CD pipeline to measure the energy usage of software following the guide documented in KDE Eco Handbook

To measure energy usage of a software we need to prepare three scripts baseline.sh , idle.sh and sus.sh and push them via MR to this repository, SUS or Standard User Scenario script emulates a standard user in a automated manner without human intervention, more on how to prepare SUS can be found here.

Currently, these scripts rely on xdotool to simulate user interactions. However, xdotool does not work on Wayland. Since the KEcoLab computer have recently migrated to Fedora 43, which uses Wayland by default, the existing scripts no longer work. To solve this problem,

I am working with my mentors Joseph, Aakarsh, and Karanjot to port the existing test scripts from xdotool to ydotool and kdotool, which are compatible with Wayland. As part of this effort, I’ve written a guide explaining how to use ydotool and kdotool more easily.

Setup ydotool and kdotool #

To test the scripts you have written it’s recommended to setup locally.

Note: Some features changes and some are removed with updates so it’s recommended to build them from source mentioned here

ydotool #

  1. Clone the repository and navigate into it
 git clone -b feat/setup-script https://invent.kde.org/neogg/ydotool.git
 cd ydotool 
  1. Run the setup script
chmod +x setup.sh
./setup.sh install
  1. Give ydotoold permission to uinput so it can run without root
sudo touch /etc/udev/rules.d/99-uinput.rules
echo 'KERNEL=="uinput", MODE="0660", GROUP="input"' | sudo tee /etc/udev/rules.d/99-uinput.rules > /dev/null
sudo usermod -aG input "$USER"

sudo udevadm control --reload-rules
sudo udevadm trigger

Note: A reboot (or full logout/login) is required for the input group change to take effect.

  1. Reboot your computer and run
systemctl --user start ydotoold

Verify that you can use ydotool by ydotool type "Hello" you will see Hello typed if installation was successful.

kdotool #

  1. Clone the repository and navigate into it
 git clone -b feat/setup-script https://invent.kde.org/neogg/kdotool.git
 cd kdotool 
  1. Run the setup script
chmod +x setup.sh
./setup.sh install

Verify that you can use kdotool by kdotool -h

How to use ydotool and kdotool to perform various actions #

Press keys #

For pressing keys we use ydotool , the syntax for pressing keys is a little confusing when you look at it :

ydotool key KEY_CODE:ACTION

KEY_CODE

Linux uses a file named input-event-codes.h that defines numeric codes for everything an input device can generate in Linux. Simply speaking for every type of keypress there is a numerical representation. For example, the LEFTCTRL key is represented by 29, and the letter T is represented by 20. How to find the code for the key you want to press , You can read the original .h file by nano /usr/include/linux/input-event-codes.h if you are in a linux system ( i hope you are using one). or inspect the file here

I use grep to find the code for the key i want since the header file is too long grep KEY_LEFTCTRL /usr/include/linux/input-event-codes.h and so on.

ACTION

  • 1 : key is pressed
  • 0 : key is released

So to press and release Ctrl you can use the following command

ydotool key 29:1 29:0

Press key combinations #

Key combinations is the same as above , you just need to keep all the keys pressed. So to press Ctrl+Alt+T which opens up a terminal you can use the following command

ydotool key 29:1 56:1 20:1 29:0 56:0 20:0

This translates to Ctrl(pressed) Alt(pressed) T(pressed) Ctrl(released) Alt(released) T(released)

This will hopefully open up a terminal.

Click ( left, right, double) #

For performing mouse clicks we can use the click command in ydotool , If you are do not like codes unlike me,I am sorry but again we have codes…

ydotool click OPTIONS BUTTON_CODE

OPTIONS

--repeat=N : can be used to press a button N times
--P : can be used to press and release a button as that's what we mostly do

BUTTON_CODE

This is a hexadecimal value that represents different mouse buttons (left, right, middle, etc.).
Yes, it looks ugly at first, but you can always open up my blog.

ydotool click 0xC0 # left click
ydotool click 0xC1 # right click
ydotool click 0xC2 # middle button click (scroll button)

ydotool click 0x40 # left button down (press)
ydotool click 0x80 # left button up (release)

ydotool click 0x41 # right button down (press)
ydotool click 0x81 # right button up (release)

And some combinations This can be useful to select text, drag and drop etc..

ydotool click 0x40 # left down 
ydotool mousemove 300 0 # drag right 
ydotool mousemove 0 200 # drag down 
ydotool click 0x80 # left up

same as above but more reliable with delays to simulate a real user

ydotool click 0x40
ydotool mousemove -D 20 300 0
ydotool mousemove -D 20 0 200
ydotool click 0x80

Type text #

Typing text is pretty simple using ydotool

ydotool type "string-you-want-to-type"

Focus a specific app #

kdotool search --name "Window Title" windowactivate
kdotool search --class appname windowactivate
kdotool search --classname org.kde.app windowactivate ( for kde apps )

Send mouse and keyboard commands to a specific window #

Focus the window using kdotool than execute commands using ydotool since commands are always executed in the active window.

Get current mouse location #

We can use kdotool for getting the current mouse location which also returns the windowID along with x and y coordinate of current mouse location

kdotool getmouselocation

The output :

x:42 y:96 screen:0 window:{e8aeec46-5c45-42cc-9839-8ad2edcb7f4f}

If you only want to extract the x and y value since thats what’s more important you can do that using regex

# Read mouse location
loc="$(kdotool getmouselocation)"

# Extract x and y
x=$(echo "$loc" | sed -n 's/.*x:\([0-9-]*\).*/\1/p')
y=$(echo "$loc" | sed -n 's/.*y:\([0-9-]*\).*/\1/p')

Move mouse to a specific coordinate #

Note : We can use ydotool to move the mouse but that may not give us any visual feedback if you are using it in a VM, that is you cannot see the actual mouse cursor move when the commands are executed but you can use the kdotool getmouselocation to see how the mouse location gets updated.

There is a command in ydotool

ydotool mousemove --absolute -x x-value -y y-value

but that does not work in Wayland properly.

We have an another command that moves the mouse relative to current mouse location so we can use that.

ydotool mousemove -x x-value -y y-value
  1. mousemove -9999 -9999 ( moves the mouse to top left 0,0)
  2. mousemove x y ( now we move relative to 0,0 so that’s like absolute mousemove)

Move mouse inside a specific window #

Since we cannot directly send mouse events to a window on Wayland, we rely on window geometry that we can get using kdotool and do relative mouse movement using ydotool.

The steps are:

  1. Focus the window
  2. Get the window geometry
  3. Reset the mouse to (0,0)
  4. Move the mouse relatively using the window coordinates
# First, focus the window:
kdotool search --class firefox windowactivate

# Get the window geometry:
kdotool search --class firefox getwindowgeometry
# Example output:
# Window {a24d3d34-85f6-4915-821b-54a71a959f6a}
# Position: 340.23748404968967,68.49159882293117
# Geometry: 768x864


#Reset the mouse position to the top-left corner of the screen:
ydotool mousemove -x -9999 -y -9999

# Move the mouse to the Position you got from above command (top left corner of the window )

# Now move the mouse inside the window (top-left corner of the window):
ydotool mousemove -x 120 -y 80

#At this point, the mouse cursor is inside the target window. so you can now move inside the window , but only once than again repeat the above steps once again ( i will think of a script to automate this)

Move mouse to the center of a window #

Moving to the center of a window uses the same idea, but instead of moving to x and y, we add half the width and height.

From the geometry we extract : x=120 y=80 width=1280 height=800

Calculate the center:

center_x = x + width / 2

center_y = y + height / 2

Reset the mouse again:

ydotool mousemove -x -9999 -y -9999

Move the mouse to the center of the window:

ydotool mousemove -x center_x -y center_y

These completes all the basic actions that are required to automate user actions , We may need to combine many of above to do tasks that simulate real user.

In the last month, I have mostly been working on refactoring the Kdenlive keyframes system to make it more powerful. This is part of a NGI Zero Commons grant via NLnet.

Improving existing parameters handling

A first step in preparation for this work was to improve the usability of some effects. In Kdenlive, we support several effect libraries, like MLT, Frei0r and FFmpeg's avfilters. Not all effects expose their parameters in the same way. For example in MLT, we have a rectangle parameter allowing to define a zone in a video frame. Other effects expose several independent parameters for a rectangle, namely x, y, width and height.

Currently, these effects are displayed with a list of sliders for each value:

With my latest changes, these will be handled like a rectangle, which means it can directly be manipulated in the monitor overlay.

Another improvement is the addition of Point parameters which will allow selecting a point in the monitor.

These changes are planned to be in the 26.04 release.

Boosting keyframes

The next improvement being worked on is support of keyframes per parameter. Currently, once you add a keyframe to an effect, it is applied to all parameters, which is sometimes not wanted. This will also allow animating only one parameter while leaving the others parameters fixed. Below is a screenshot of a basic UI created to test the feature.

Also, currently you cannot see keyframes for different effects at the same time. Regrouping keyframes for all effects in one place will enable more powerful editing.

Kdenlive needs your support

Our small team has been working for years to build an intuitive open source video editor that does not track you, does not use your data, and respects your privacy. However, to ensure a proper development requires resources, so please consider a donation if you enjoy using Kdenlive - even small amounts can make a big difference.

After taking a 13 year hiatus from KDE development, Harald Sitter's talk on KDE Linux at Akademy 2024 was the perfect storm of nostalgia and inspiration to suck me back in. I've been contributing on and off since then.

This blog post outlines some gaping holes I see in its extensibility model, and how I plan to address them (assuming no objections from other developers).

banana

The Problem

KDE Linux is being built as an immutable OS without a traditional package manager. The strategy leans heavily on Flatpak for GUI applications, which (though, not without its problems) generally works well for its stated goal. But here's the thing: the Linux community has a relatively large population of CLI fanatics—developers who live in the terminal, who need $OBSCURE_TOOL for their workflow, who won't be satisfied with just what comes in a Flatpak.

The OS ships with a curated set of developer tools that we KDE developers decided to include. Want something else? There's a wiki page with suggestions for installation mechanisms we don't officially support—mechanisms that, let's be real, most of us don't even use ourselves.

This sets us up for the same reputation trap that caught KDE Neon:

Just like KDE Neon got pigeonholed with the reputation of being "for testing KDE software," KDE Linux risks getting branded as "for developing KDE software only."

There's also a deeper inconsistency here. One of the stated goals is making the end user's system exactly the same as our development systems. But if the tools we actually use day-to-day are already baked into the base image—and thus not part of the extensibility model we're asking users to adopt—then we're not eating our own dog food. We're shipping an experience we don't fully use ourselves.

The Solution

whale

Look at the wild success of Docker and Kubernetes. Their container-based approach proved that immutable infrastructure actually works at scale. That success paved the way for Flatpak and Snap to become the de facto solution for GUI apps, and now we're seeing immutable base systems everywhere. The lesson is clear: containers aren't just one solution among many—they're the foundation that makes immutable systems viable.

Containers for CLI Tools???

As crazy as it sounds, that's the logical next step. Let's look at the candidates to base our solution on top of:

distrobox/toolbox are trying to solve the right problem—building long-term, persistent development environments—but they're doing it on top of docker/podman, which were designed for ephemeral containers. They're essentially fighting against the grain of their underlying systems. Every time you want to do something that assumes persistence and state, you're working around design decisions made for a different use case. It works, but you can feel the friction.

systemd-nspawn is built for persistence from the ground up, which is exactly what we want. It has a proper init system, it's designed to be long-lived. The challenge here is that we need fine-grained control over the permissions model—specifically, we need to enable things like nested containers (running docker/podman inside the container) and exposing arbitrary hardware devices without a lot of manual configuration. systemd-nspawn makes these scenarios difficult by design, which is great for security but limiting for a flexible development environment.

devcontainers nail the developer experience—they're polished, well-integrated, and they just work. The limitation is that they're designed to be used from an IDE like VS Code, not as a system-wide solution. We need something that integrates with the OS itself, not just with your editor. That said, there's definitely lessons to learn from how well they've executed on the developer workflow.

Our knight in shining armor:

incus icon

Enter Incus. It checks all the boxes:

  • Proper API for building tooling on top of it
  • Nested containers work out of the box—want to run docker inside your Incus container? Go for it
  • Privileged container mode for when you need full system access and hardware devices
  • Built on LXC, which means it's designed for long-lived, system-level containers from day one, not retrofitted from ephemeral infrastructure

Bonus: it supports VMs too, for running less trusted workloads. People on Matrix said they want this option. I don't fully get the use case for a development environment, but the flexibility is there if we need it.

Architecture

Incus exposes a REST API with full OpenAPI specs—great for interoperability, but dealing with REST/OpenAPI in C++ is not something I'm eager to take on.

My first choice would be C#—it's a language I actually enjoy, and it handles this kind of API work beautifully. But I suspect the likelihood of KDE developers accepting C# code into the project is... low.

Languages that already build in kde-builder and CMake will probably have the least friction for acceptance, and of those, Python is the best fit for this job. The type system isn't as mature as I'd like (though at least it exists now with type hints), and the libraries for OpenAPI and D-Bus are... okay-ish. Not amazing, but workable.

Here's the plan:

  • Daemon in Python to handle all the Incus API interaction
  • CLI, KCM, and Konsole plugin in C++ for the user-facing pieces that integrate with the rest of KDE

This way we keep the REST/OpenAPI complexity in Python where it's manageable, and the KDE integration in C++ where it belongs.

Current Status

Look, the KDE "K" naming thing is awesome. I'm not going to pretend otherwise. My first instinct was "Kontainer"—obvious, descriptive, checks the K box. Unfortunately, it was already taken.

So I went with Kapsule. It's a container. It encapsulates things. The K is there. Works for me.

The implementation is currently in a repo under my user namespace at fernando/kapsule. But I have a ticket open with the sysadmin team to move this into kde-linux/kapsule. Once that's done I'll be able to add it to kde-builder and start integrating it into the KDE Linux packages pipeline.

The daemon and CLI are functional. Since a picture is worth a thousand words, here's a screenshot of the CLI in action:


Here's docker and podman running inside ubuntu and fedora containers, respectively:


And here's chromium running inside the container, screencasting the host's desktop:


Next Steps

Deeper integration

Right now, Kapsule is a CLI that you have to manually invoke, and it lives kind of separately from the rest of the system. That's fine for a proof of concept, but the real value comes from making it invisible to users who just want things to work.

Konsole

Konsole gained container integration in !1171, so I just need to create and add an IContainerDetector for Kapsule. Once that's wired up, I'll add a per-distro configurable option to open terminals in the designated container by default.

When Kapsule is stable enough, that becomes the default behavior. Users won't have to know or care about Kapsule—they just open a terminal and their tools are there. Unless they break their container, which leads nicely to the next point...

KCM

A System Settings module for container management:

  • Create, delete, start, stop containers
  • Easy reset if you ran something that broke things
  • For advanced users: configuration options like which distro to use, resource limits, etc.

Discover

These containers need to be kept up to date. Most will have PackageKit inside them, so we can create a Discover plugin that connects to the container's D-Bus session and shows updates for the container's packages alongside the host's updates. Seamless.

Moving dev tools out of the base image

This is the long-term goal: get Kapsule stable and good enough that we can remove ZSH, git, clang, gcc, docker, podman, distrobox, toolbox, and the rest of the dev tools from the base image entirely. All of those already work in Kapsule.

Once that happens, we're eating our own dog food. The extensibility model we're asking users to adopt is the same one we're using ourselves.

Pimped out container images

We'll maintain our own image repository. There's no real limit to the number of images we can offer, and everyone in the #kde-linux channel can show off their style. Want a minimal Arch-based dev container? A fully-loaded Fedora workstation? A niche distro for embedded development? A Nix-based image (I'm looking at you, Hadi Chokr)? All possible.

Trying it out

Honestly, the best way to try it out is to wait for me to get it integrated into the KDE Linux packages pipeline and into the base image itself. Hopefully that'll be in the next few days.

Thursday, 5 February 2026

This particular guides are for myself in which i made mistakes and so that i won’t repeat them agian.
1. Environment Preparation First, install all necessary libraries for the Qt6/KF6 stack.