Albin Larsson: Blog

Culture, Climate, and Code

Scripting KDE Konsole

21st October 2024

I finally got(took) the time to automate away the 2 seconds it takes me to set up my Konsole development layout. Given the sparse examples showing Konsole’s DBus interface and its quirks in action, I thought it made sense to share my script here.

Having recently moved from Bash to Fish, I rediscovered the joy of shell scripting and added some git and just checks as well.

The following Fish function results in a three-pane window with Vim to the right taking up 65% of my screen leaving two top-to-bottom split panes showing potential information from git or just.

function git_summary -d "check if you are in a git repository and show git-related information"
    if git rev-parse --is-inside-work-tree >/dev/null 2>&1
        git status
        git worktree list
    else if test -d .git
        git worktree list
    end
end

function just_summary -d "check if a justfile exists and list the available commands"
    if test -f justfile
        just --list
    else
        echo "No justfile found"
    end
end

function dev -d "From the existing session setup a three-pane layout with git status, nvim, and just --list"
    # Setup layout
    set PRIMARY_SESSION_ID (qdbus $KONSOLE_DBUS_SERVICE /Windows/1 org.kde.konsole.Window.currentSession)

    qdbus $KONSOLE_DBUS_SERVICE /konsole/MainWindow_1 org.kde.KMainWindow.activateAction split-view-left-right > /dev/null
    set EDITOR_SESSION_ID (qdbus $KONSOLE_DBUS_SERVICE /Windows/1 org.kde.konsole.Window.currentSession)

    qdbus $KONSOLE_DBUS_SERVICE /Windows/1 org.kde.konsole.Window.setCurrentSession $PRIMARY_SESSION_ID

    qdbus $KONSOLE_DBUS_SERVICE /konsole/MainWindow_1 org.kde.KMainWindow.activateAction split-view-top-bottom > /dev/null
    set SECONDARY_SESSION_ID (qdbus $KONSOLE_DBUS_SERVICE /Windows/1 org.kde.konsole.Window.currentSession)

    # NOTE: qdbus does not appear to work with the resizeSplits method: https://invent.kde.org/utilities/konsole/-/merge_requests/932#note_837270
    gdbus call --session --dest $KONSOLE_DBUS_SERVICE --object-path /Windows/1 --method org.kde.konsole.Window.resizeSplits 0 "[35, 65]" > /dev/null

    qdbus $KONSOLE_DBUS_SERVICE /konsole/MainWindow_1 org.qtproject.Qt.QWidget.showFullScreen

    # Finally run the various commands in the right panes
    qdbus $KONSOLE_DBUS_SERVICE /Sessions/$EDITOR_SESSION_ID org.kde.konsole.Session.runCommand "nvim ."
    qdbus $KONSOLE_DBUS_SERVICE /Sessions/$PRIMARY_SESSION_ID org.kde.konsole.Session.runCommand "git_summary"

    # TODO: it seems like bash is always initiated before fish, for an unknown reason
    set FUNCTION_FILE (status --current-filename)
    qdbus $KONSOLE_DBUS_SERVICE /Sessions/$SECONDARY_SESSION_ID org.kde.konsole.Session.runCommand "fish -c 'source $FUNCTION_FILE; just_summary'"
end

Note that $KONSOLE_DBUS_SERVICE is only available from Konsole itself, some alternatives are available as per the Konsole manual. I wasn’t able to get qdbus to work with Qt arrays so I used gdbus(I’m on a Gnome-based system but I prefer the syntax of qdbus.). It’s odd that in my final panel, I need to run fish manually as my default shell is fish, I’m a little worried that there is an underlying problem.

Reconcile Against any MediaWiki Instance

5th August 2024

You have just deployed your 55th reconciliation service for a MediaWiki or Wikibase-based service. You start wondering if this isn’t the time to stop copy-pasting your code around and apply some of that don’t-repeat-yourself wisdom. That was me a moment ago after a few months of pushing it in front of me. Now a prototype intended to replace all of our reconciliation services targeting MediaWiki and Wikibase is here and you can give it a go in OpenRefine!

Let’s jump in head first with some example endpoints:

https://kartkod.se/apis/reconciliation/mw/en.wikipedia.org/ # English Wikipedia (MediaWiki)
https://kartkod.se/apis/reconciliation/wb/wikibase.world/ # Wikibase World (Wikibase)
https://kartkod.se/apis/reconciliation/wb/www.wikidata.org/ # Wikidata (Wikibase)
https://kartkod.se/apis/reconciliation/mw/www.wikidata.org/ # Wikidata (MediaWiki)
https://kartkod.se/apis/reconciliation/mw/en.wikisource.org/ # English Wikisource (MediaWiki)
# your wiki? and many more!

You see the patterns, now let’s see how we got here. Once you realize you can put any domain in there, you might think that we made it very error-prone and complex. We did, here is why:

It’s not error-prone to us

You see, our OpenRefine users can’t add their own reconciliation services and our collection of services is already there the first time they sign in.

We want it to be open to you

If we needed to allow-list all the configurations it would quickly become less useful to others and we need it to hook into our other reconciliation services with local restrictions anyway. It’s an experiment and I hope we can keep it configuration less.

We need it to be stateless

We deploy all our reconciliation services in a distributed way and keeping our services stateless is incredibly useful to limit the maintenance and engineering needed.

Future work

This service is experimental and you should expect it to change in the coming weeks as we bring it on pair with our existing services. It will never implement the whole Reconciliation specification but

A note on compatibility with the OpenRefine Wikibase extension, our reconciliation services consider the Wikibase URIs as the “true” identifiers while the Wikibase extension expects just the QID. Hopefully, this is something I can work on upstream as it’s not an option to change the behavior on our end. In the meantime you might be able to work around this using the “Use values as identifiers” feature.

I hope you give it a try, if you have any questions or suggestions feel free to reach out. I will also be at Wikimania if you want to chat in person!

EXIF to GeoJSON Converter

19th July 2024

I made a small tool for extracting EXIF location data and “converting” it into GeoJSON. It comes from my need to display EXIF locations in OpenOrienteering Mapper. Turns out it’s useful for other things like OpenStreetMap mapping and Wikidata(WikiShootMe supports custom GeoJSON layers).

You find the tool on this webpage and the code over at Codeberg.

Screenshot of the tool.

Cloudflare Worker to resolve URLs

1st March 2024

The other day, I needed to resolve a w.wiki URL from a client-side application. However, the UrlShortener MediaWiki extension does not provide an API for resolving URIs (T358049), and client-side applications can’t simply resolve the URLs normally due to CORS.

To unblock myself, I decided to write a generic Cloudflare Worker to resolve URLs, as it is a common task and I always end up dealing with the same edge cases, such as content negotiation and URL fragments. I will update the code below as I need to handle more cases.

// Created by Albin Larsson and made available under Creative Commons Zero
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  const urlParam = new URL(request.url).searchParams.get('url');

  if (!urlParam) {
    return new Response('URL parameter is missing.', { status: 400 });
  }

  const corsHeaders = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
  };

  // manually follow redirects to obtain the final URL from the location header
  // as the client-side only fragment wont be a part of the URL-object
  let finalUrl = urlParam;
  let response;

  do {
    // pretent to be human by requesting text/html to prevent default content-neogtiation
    response = await fetch(finalUrl, { redirect: 'manual', headers: { 'Accept': 'text/html' } });

    if (response.status >= 300 && response.status < 400) {
      const redirectUrl = new URL(response.headers.get('location'), finalUrl);
      finalUrl = redirectUrl.href;
    }
  } while (response.status >= 300 && response.status < 400);

  return new Response(finalUrl, {
    headers: {
      ...corsHeaders
    }
  });
}

Older Posts