Skip to content

Tuesday, 15 June 2021

For years, most development discussion for Krita has happened on the #krita channel on the Freenode IRC network. IRC is a venerable chat system (that’s to say, it’s old and quirky) but it works very well for us because it’s free and open source software and because it treats chat as chat: it doesn’t keep logs for you if you’re not in the channel, there are many clients and interaction is simple and limited to just text.

However, the freenode IRC network is no longer a good host for our development work. The people currently managing the network are doing very strange things, and the people who used to manage the network have created a new network, libera.chat.

From today, if you want to chat to Krita’s developers and contributors, you’ll need to join the #krita channel on libera.chat.

Bridges with the Matrix network (a different, newer, more complex chat system) are in the works, and sometimes work, but sometimes don’t work. This means that if you join use Matrix to join the IRC channel people probably will see nothing of what you’re saying.

The post Developer chat moving appeared first on Krita.

Qt's networking code has always been one of its more obtuse parts, requiring using signals for something that didn't quite seem right for them. A linear flow of code would become a jumbled mess of member functions and signal connections.

When developing Challah's netcode, I quickly realised this wasn't going to suffice for the large amount of it I was going to be writing. Thus begins my journey through signal hell, arriving at many bumps before I discovered that integrating Qt's networking stuff with coroutines is possible.

Stop Zero

If you're just here to see some cool coroutine code, you can look at https://invent.kde.org/-/snippets/1711 for an single-file version of the tutorial in this blog-post, with a coroutine function already made and ready for you to play with. If you want something more full-fledged, look at https://invent.kde.org/cblack/coroutines.

But if you want to know what's actually going on behind the scenes, continue reading.

Stop One: Don't Do This

Approach one that I took to get out of signal hell was a simple while loop:

while (!reply->isFinished()) {
    QCoreApplication::processEvents();
}

This is a bad idea. Strange graphical glitches, bugs, crashes, etc. lie behind this innocuous code. Don't do it. Forcibly spinning the event loop in a lot of places causes a lot of bugs.

Step Two: Callback Hell

So, what do you do if you want to write somewhat linear code without doing that? Callbacks.

QObject::connect(val, &QNetworkReply::finished, [val, callback]() {
    if (val->error() != QNetworkReply::NoError) {
        val->deleteLater();
        callback({QStringLiteral("network failure(%1): %2").arg(val->error()).arg(val->errorString())});
        return;
    }
    
    auto response = val->readAll();
    
    protocol::chat::v1::CreateGuildResponse ret;
    if (!ret.ParseFromArray(response.constData(), response.length())) {
        val->deleteLater();
        callback({QStringLiteral("error parsing response into protobuf")});
        return;
    }
    
    val->deleteLater();
    callback({ret});
    return;
});

Every time I had to call into the netcode, I would have to provide a std::function<void(std::variant<Result,Error>)>. It's passable for a single call, but chaining them quickly violates “happy path sticks to the left”, making you wonder if you're actually writing Python with how far indented your code is.

c->call([c](auto response) {
  c->call([c](auto response) {
    c->call([c](auto response) {
      c->call([c](auto response) {
        c->call([c](auto response) {
        });
      });
    });
  });
});

Not good.

Step Three: Out of the Inferno, Into the Frying Pan

await/async as a mechanic in languages generally reduces the amount of callback hell you face by sugaring it for you. For example, in JS, this:

return fetch().then((it) => {
    return it.text
})

is equivalent to this:

return await fetch().text

Both of these return the exact same thing to the caller, but one is much easier to deal with when chained.

For a while, C++ didn't have this. Of course, with C++20, coroutines with co_await & company are now available* in most compilers.

Diversion: That Asterisk

Coroutines are technically available in most compilers, but you're going to have to do a lot of compiler-specific dances in both your code and the build system in order to pass the correct flags to enable coroutines.

I use this snippet to handle clang & gcc:

#if defined(__clang__)

#include <experimental/coroutine>
namespace ns = std::experimental;

#elif defined(__GNUC__)

#include <coroutine>
namespace ns = std;

#endif

where ns is aliased to the namespace containing coroutine-related stuff. Also note that clang will probably fail to compile stuff due to experimental/coroutine being part of libc++ while the rest of your system probably uses gcc's libstdc++. So, for all intents and purposes, gcc is your only option with coroutines on Linux.

Anyways...

Unhelpful Documentation

When hacking on coroutines, I quickly realised: the available documentation on what they were was both young and immature, as well as unhelpful in what they did have. So, this blog post is going to document them.

Parts of the Coroutine

Coroutines are largely split into two parts: the “promise” and the “awaitable”.

The promise is responsible for handling the “internal” side of any coroutine: it's what provides the return value, its yield behaviour, handling unhandled exceptions, the “await transformer”, and some other things. All coroutines need to return a type associated with one of these.

The awaitable handles the “external” side of the coroutine being co_awaited: it checks with the outside world whether or not the coroutine needs to suspend, listening to the outside world and reactivating the coroutine when it's ready, and providing the value received from the coroutine's completion.

As far as stuff that's directly interacting with coroutines goes, that's mostly it. However, there's still one type you need for an end-user API: the type returned by a coroutine, which is what the compiler uses to match your coroutine to a promise type.

SomeType a_coroutine() {
    auto foo = co_await another_thing();
    // ...
} 

This SomeType is what I will be calling the “future” or the “task” type, as it's basically the QFuture of the QPromise (or QFutureInterface to speak in Qt5 parlance).

C++ coroutines are very much “bring your own runtime and types”, and thankfully, that's what Qt already has, making it perfect for integrating into coroutines.

A QML-friendly future

C++ coroutines are the perfect fix to a long-standing issue in QML: representing asynchronous results in the UI in a manner that being an object property or model data can't.

So, let's get to constructing the base of our coroutines, and a type that will be suitable for passing into QML.

For ease of use, we'll want this done as a copyable Q_GADGET type with implicitly shared data. This will help us later down the road when we're passing the future type through lambdas.

(For the sake of simplicity of teaching how coroutines work, I'll be doing a simple non-template future type that talks in QVariants and doesn't have success/failure states. If you want to see code with more type safety and success/failure states, you can check out https://invent.kde.org/cblack/coroutines.)

We'll start by defining our shared data & behaviour.

class Future
{
    Q_GADGET

    struct Shared {
        QVariant result;
        std::function<void(QVariant)> then = [](QVariant) {};
        bool done = false;
    };
    QSharedPointer<Shared> d;

public:

    Future() {
        d.reset(new Shared);
    }
    Future(const Future& other) {
        this->d = other.d;
    }
};

This gives us a Future that is effectively a blob. First, let's define getters for done and result, so the outside world can actually tell what the current state of the Future is:

bool settled() const {
    return d->done;
}
QVariant result() const {
    return d->result;
}

Now we need a way to say that the future is completed and a result is available.

void succeed(const QVariant& value) const {
    if (d->done) {
        return;
    }
    d->done = true;
    d->result = value;
    d->then(d->result);
}

We don't want to trigger the callback again if we've already marked the future as done, so we abort early if we're already done.

You may notice that this function, despite being a setter semantically, is marked as const. This is mostly for ease of dealing with in lambdas later on.

Now, we need a way to register the callback function in the C++ side of things.

void then(std::function<void(QVariant)> then) const {
    d->then = then;

    if (d->done) {
        then(result());
    }
}

If the future is already done and you register a callback, you want to call it immediately. This is to handle situations like this:

Future fetch() {
  if (cache.has_thing()) {
    Future future;
    future.succeed(cache.get_thing();
    return future;
  }
  // ...
}
void main() {
    fetch().then([](QVariant) {
    });
}

If we didn't invoke the callback in then(), the callback would never be triggered as the future was returned to the caller in an already succeeded status.

Since I promised a QML-friendly future type, we should implement that now.

Q_INVOKABLE void then(const QJSValue& it) {
    then([va = it](QVariant result) mutable {
        va.call({va.engine()->toScriptValue(result)});
    });
}

We add a Q_INVOKABLE overload that takes a QJSValue, as JS functions are represented as QJSValues in C++. We then have a lambda which we pass back into the other then function, taking the QVariant, transforming it into a QJSValue, and calling the JavaScript callback with the variant.

JS usage looks like this:

future.then((it) => {
    console.warn(it)
})

If you've been following along, you should now have this class:

class Future
{

    Q_GADGET

    struct Shared {
        QVariant result;
        std::function<void(QVariant)> then = [](QVariant) {};
        bool done = false;
    };
    QSharedPointer<Shared> d;

public:

    Future() {
        d.reset(new Shared);
    }
    Future(const FutureBase& other) {
        this->d = other.d;
    }

    bool settled() const {
        return d->done;
    }
    QVariant result() const {
        return d->result;
    }

    void succeed(const QVariant& value) const {
        if (d->done) {
            return;
        }
        d->done = true;
        d->result = value;
        d->then(d->result);
    }

    void then(std::function<void(QVariant)> then) const {
        d->then = then;

        if (d->done) {
            then(result());
        }
    }
    Q_INVOKABLE void then(const QJSValue& it) {
        then([va = it](QVariant result) mutable {
            va.call({va.engine()->toScriptValue(result)});
        });
    }
};

The Backing Promise

Of course, the above class doesn't make a coroutine. For this, we need to implement the promise type.

For placement options, you have two places where you can put this: inside the future type itself, or outside of the type in an explicit coroutine_traits template specialisation.

The former would look like this:

class Future {
   // ...
   struct promise_type {
   };
};

While the latter would look like this:

template<typename ...Args>
struct ns::coroutine_traits<Future, Args...> {
    struct promise_type {
    };
};

(where ns is the namespace with the coroutine types, std:: on gcc or std::experimental on clang.)

The latter allows you to implement a promise type for any type, not just one that you have control of the declaration of.

For this blog post, I'll be going with the latter approach.

struct promise_type {
};

One of the things we'll need in our promise type is a member variable to hold the future type.

struct promise_type {
    Future _future;
};

Of course, the compiler doesn't know about this member variable, so we need to start implementing the promise_type interface:

struct promise_type {
    Future _future;
    Future get_return_object() noexcept {
        return _future;
    }
};

T get_return_object() noexcept is the exact type signature that needs to be implemented. We use noexcept here, as exceptions can cause a double free, and therefore, a crash.

Now, we need to implement two things: initial_suspend() and final_suspend(). These two functions are called at the start and end of your coroutine, and have to return a co_awaitable type.

In an expanded form, initialsuspend and finalsuspend look like this:

Future your_coroutine() {
    co_await promise.initial_suspend();
    // coroutine body here...
    co_await promise.final_suspend();
}

You could theoretically return anything you want here, but you'll likely be sticking with ns::suspend_never, which is an awaitable value that resumes execution of the coroutine immediately when co_awaited, and what we'll be using for this blog post.

Add the implementation to your promise type:

ns::suspend_never initial_suspend() const noexcept { return {}; }
ns::suspend_never final_suspend() const noexcept { return {}; }

Your promise is responsible for taking care of any values that are co_returned with the return_value function.

I recommend having two overloads: const T& (copy value) and T&& (move value).

For our promise_type, the implementation of those will simply look like this:

void return_value(const QVariant& value) noexcept
{
    _future.succeed(value);
}
void return_value(QVariant &&value) noexcept
{
    _future.succeed(std::move(value));
}

If your coroutine was returning something like a std::unique_ptr<T>, you would need the std::move in order to properly handle it, and thus why I say you should have this overload in your promise type.

With getreturnobject, initialsuspend, finalsuspend, and return_value, you only have one remaining function left to implement in your coroutine. It's quite simple.

void unhandled_exception() noexcept {

}

This is what gets called in the catch block of a try/catch block catching exceptions that the coroutine might throw. Use std::current_exception() to access the exception.

For this blog post, we'll simply be doing Q_ASSERT("unhandled exception"); in our implementation.

With that, the promise type is complete. It should look something like this:

struct promise_type {
    Future _future;

    Future get_return_object() noexcept {
        return _future;
    }

    ns::suspend_never initial_suspend() const noexcept { return {}; }
    ns::suspend_never final_suspend() const noexcept { return {}; }

    void return_value(const QVariant& value) noexcept
    {
        _future.succeed(value);
    }
    void return_value(QVariant &&value) noexcept
    {
        _future.succeed(std::move(value));
    }

    void unhandled_exception() noexcept {
        Q_ASSERT("unhandled exception");
    }
};

Remember that this needs to be within the Future type itself, or within the appropriate coroutine_traits overload.

The Awaitable

With the Future and promise completed, we don't have much left to do. We now need to implement operator co_await(Future) in order to allow the Future to be awaited.

Technically, we don't need the promise type to make a Future awaitable, only the Future and the co_await overload. Implementing a promise type allows your coroutine that's co_awaiting a Future to return another Future.

There's not really much to the co_await overload, so I'll just paste an implementation verbatim:

auto operator co_await(Future it) noexcept
{
    struct Awaiter {
        Future future;

        bool await_ready() const noexcept {
            return future.settled();
        }
        void await_suspend(ns::coroutine_handle<> cont) const {
            future.then([cont](QVariant) mutable {
                cont();
            });
        }
        QVariant await_resume() {
            return future.result();
        }
    };

    return Awaiter{ it };
}

co_await needs to return a type with the following methods:

bool await_ready() const noexcept: This function is called before suspending the coroutine to wait on the awaitable to resolve. If the function returns true, the coroutine continues execution without suspending. If the function returns false, it suspends and waits on the value.

Think back to the example of a function returning an already succeeded future to see why this is useful. If the future already holds a value, there's no need to waste time suspending the coroutine.

void await_suspend(ns::coroutine_handle<> callback) const: This function is called when the coroutine suspends with a coroutine handle passed in. For all intents and purposes, you can treat it as if it were a std::function holding a callback, except operator () resumes the coroutine instead of calling a function.

You call the coroutine_handle to resume the coroutine when whenever it's waiting on is ready, e.g. when the future in this example is marked as succeeded. You can connect this to just about anything, e.g. a QTimer::singleShot, a signal, using it as a callback, etc.

T await_resume() const: This is the value that gets assigned to the result of co_awaiting the expression:

auto it = co_await fetchFromNetwork();
  // |
  // ^ this is the value from await_resume()

Annd with all three types implemented, you now have a QML-friendly future, a promise, and an awaitable.

A Brand New Washing Machine

With your new coroutine, you can do just about anything asynchronously. This is an example function that returns a future that will be fufilled in N miliseconds:

Future timer(int duration) {
    Future it;

    QTimer::singleShot(duration, [it]() {
        it.succeed({});
    });

    return it;
}

Note that while it returns a Future, it is not a coroutine. It does not co_await. This is a completely normal function that you can call however you desire.

Making a coroutine using this function is easy:

Future thisIsACoroutine() {
    co_await timer(2000);

    co_return {};
}

Note that this function could not return void. The return type of a coroutine has to have an associated promise type, which void does not.

In a class exposed to QML...

class Singleton : public QObject
{
    Q_OBJECT

public:
    Q_INVOKABLE Future foo() {
        co_await timer(2000);

        co_return "i was returned by a coroutine";
    };
};

This can be used like so:

Button {
    anchors.centerIn: parent
    text: "a coroutine!"
    onClicked: Singleton.foo().then((val) => {
        console.warn(val)
    })
}

This will output the following in your console 2 seconds after every time you press this button:

qml: "i was returned by a coroutine"

The inline code from this blog post can be found in a single-file format at https://invent.kde.org/-/snippets/1711. Compile with C++20 and -fcoroutines-ts -stdlib=libc++ for clang, and -fcoroutines for gcc.

For a basic future+promise+awaitable deal, this is about all you need to know. However, the iceberg gets way deeper than that.

Stay tuned for more blog posts about the eldritch arcana you can pull off with coroutines. Next one will probably be about wrapping already-existing types such as a QNetworkReply* in order to make them co_await-able.

You can see more advanced (and arguably useful) code at https://invent.kde.org/cblack/coroutines.

Contact Me

Note that I wrote this blog post at 2 in the morning. If there's anything that doesn't make sense, please feel free to come to me and ask for clarification.

Or, want to talk to me about other coroutine stuff I haven't discussed in this blog post (or anything else you might want to talk about)?

Contact me here:

Telegram: https://t.me/pontaoski Matrix: #pontaoski:tchncs.de (prefer unencrypted DMs)

Tags: #libre

Tuesday, 15 June 2021. Today KDE releases a bugfix update to KDE Plasma 5, versioned 5.22.1.

Plasma 5.22 was released in June 2021 with many feature refinements and new modules to complete the desktop experience.

This release adds a week's worth of new translations and fixes from KDE's contributors. The bugfixes are typically small but important and include:

  • KWin: Platforms/drm: support NVidia as secondary GPU with CPU copy. Commit. Fixes bug #431062
  • Weather applet: Point bbcukmet to new location API. Commit. Fixes bug #430643
  • Wallpapers: Add milky way. Commit. Fixes bug #438349
View full changelog

Monday, 14 June 2021

The second maintenance release of the 21.04 series is out bringing missing keyframing support to effects (like glitch0r. scratchlines and charcoal) as well as the usual batch of bug fixes and usability improvements.

Full log

  • Remove duplicate line from last cherry-pick. Commit.
  • Hopefully proper patch to solve “white” rendering issues. Commit.
  • Fix resize clip end does not allow touching next clip. Commit.
  • Fix clip thumbs disappearing on timeline resize. Commit.
  • Fix timeline thumbnails not saved with project. Commit.
  • Don’t discard subtitle files on project fps change. Commit.
  • Update guides position on project’s fps change. Commit.
  • Fix delete selected clips not working on project opening. Commit.
  • Fix Chroma Key: Advanced edge mode normal was reset to hard. Commit.
  • Fix various frei0r effects losing parameter settings:. Commit.
  • Next try to fix keyframe view positon for mixes. Commit.
  • Revert “Fix keyframeview position in mixes”. Commit.
  • Fix keyframeview position in mixes. Commit.
  • Make effects keyframable: scratchlines, tcolor, lumaliftgaingamma. Commit. See bug #393668
  • Make effects keyframable: charcoal, dust, oldfilm, threshold.xml. Commit. See bug #393668
  • Make glitch0r effect keyframable. Commit. See bug #393668
  • Fix profile repository not properly refreshed after change. Commit.
  • Fix marker monitor overlayer flickers on hover. Commit.
  • Ensure timeline zoombar right handle is always visible. Commit.
  • Fix issue with duplicated title clips. Commit.
  • Fix effect sliders on right to left (rtl) layouts. Commit. Fixes bug #434981
  • Fix alignment of statusbar message label. Commit. Fixes bug #437113
  • Fix crash using filter with missing MLT metadata (vidstab in MLT 6.26.1). Commit.
  • Try to fix wrongly set color in titler. Commit.

So, the first week of coding period has ended. It was exciting and full of challenges. I am happy that I am on the right track and making progress as I’ve promised. This is a small summary of the work done this week.

Getting rid of compiler warnings

digiKam has a really huge codebase. It is actively developed but a lot of code is old which contains deprecated Qt code which must be fixed before introducing Qt6. These are the classes which had issues in digiKam which are now fixed :-

  • QMap
  • QProcess
  • QHash
  • QMatrix
  • QLabel
  • QString
  • QFlag
  • QTimeLine
  • QTabletEvent
  • QWheelEvent
  • QButtonGroup
  • QPrinter
  • Some namespace changes like endl to Qt::endl

These patches can be found at my work branch

And these remain which I shall fix this week:

  • QSet
  • qrand() and qsrand()

Regression testing is important for my project. I patched an important class in the core of digiKam that was used by all filters in the image editor (The MR). This had a risk of introducing regression to filters. I wrote a new unit test to verify it did not.

In a nutshell, there are currently 6693 lines of build warnings on master. See this here

With the patches on my branch these warnings have reduced to 1830 lines! That’s a reduction of about 73%. See this here.

While this is not the best way to track progress but it still gives rough estimate. We now have much cleaner build outputs. This week, it will get smaller.

Thanks for reading!

Sunday, 13 June 2021

A year ago I’d just finished my History degree and I had no idea how to code. This year, I’m taking part in Google Summer of Code! I’m super happy to get the chance to learn more about how KDE software works, and to finally contribute to a project I’ve been using for years.

Over the summer, I’ll be working with KDE developers to create productivity-focused components for Plasma Mobile that work with Akonadi, KDE’s personal information management framework. Akonadi is a super useful piece of kit: it allows developers to tap into a user’s synchronised e-mails, contacts, calendars, providing a seamless experience in productivity tools. I’ll be working on this project with my mentor Carl Schwan, who also helped me during my time doing Season of KDE, and Devin Lin.

The state of affairs

Plasma Mobile is quickly shaping up to be a viable alternative to the Android/iOS duopoly, but it’s not quite there yet. KDE developers are hard at work, nailing the basics. KDE’s Plasma provides an incredibly solid foundation, though unfortunately many of the powerful desktop applications that the KDE community are not suitable for use on mobile devices.

This is mainly due to the fact they are built with a GUI framework called KXMLGui, which allows developers to create intricate and capable user interfaces for applications ranging from video editors to word processors to web browsers. Unfortunately, it’s not just made to be convergent — i.e., adaptable to different display sizes and interaction types (touch vs mouse and keyboard).

KDE’s projects are underpinned by Qt, an immensely powerful toolkit for C++ that lets you create UIs. Thanks to efforts by the Qt team, QML (a UI markup language) and Qt Quick (QML’s standard library) provide a new way of designing UIs that are built to adapt well to different device form factors. The big brained developers at KDE then developed Kirigami, a Qt Quick framework that makes it easy for a developer to bang out a pretty and convergent application usable on the desktop and on your phone (yes, this includes Android and iOS!).

However, Kirigami is still young, and the amount of applications that use it is still growing. Currently, there are a lack of productivity applications that take advantage of Akonadi.

Psst. Thinking of building a Kirigami app? Read the tutorials I wrote on KDE Develop.

Kalendar

Fortunately, people at KDE have already got the ball rolling. My mentor Carl Schwan started work on Kalendar, a Kirigami application that lets you display the events present in your Akonadi-synchronised calendars. However, Kalendar remains a proof-of-concept… for now.

My work for GSoC will focus on turning Kalendar into a fully-usable application that will let you view, add, and edit events in all the ways that a normal calendar application would, on both your PC and your phone. This involves figuring out how event ingestion and modification works in Akonadi, how Akonadi stores and retrieves data, and a lot of UI usability testing. By the end of the summer, we should be a lot closer to having a powerful calendar you can use with your fingertips on Plasma Mobile. 🙂

First week’s work: EventEditor merge request

In this first week, some progress has been made!

Kalendar now has an event editor sheet. While still a work-in-progress, once done, it should let you add and edit events, and let you provide dates, descriptions, reminders, attendees, and more.

Here are a bunch of screenshots of the sheet. So far, the combo boxes for the date and time fields are using custom date and time picker widgets. An upstream date picker widget should be coming in a Kirigami Add-ons package soon, and it will replace the current iteration.

This sheet is very much still a work-in-progress, from both a design and functionality perspective. So far, you can add an event through the sheet, and it will be correctly ingested into Akonadi… if you select a valid calendar (the drop-down in the event editor doesn’t make it clear which calendars are editable, yet!). You can provide an event’s name, description, start/end dates and times. The attendee, reminder, and repeat options are currently non-functional, but you can bet I’m figuring those out.

Get in touch

Feel free to reach out to me if you have any feedback, or if you just want to chat – I’m @clau-cambra:kde.org on Matrix. 😃

See you next week!

Starting with the KDE Frameworks 5.84 release, KXMLGUI based applications will feature expandable tooltips per default.

The matching merge request by Felix Ernst got merged today after 3 months ;=)

What are expandable tooltips at all?

Good question ;=)

In short: for stuff like menu & toolbar actions, it provides an easy way to access the “What’s This?” help.

Unlike before, where you need to manually trigger that via the “Shift-F1” shortcut and click around to try out which GUI elements provide at all this additional help, you will now first get a small tooltip with the normal tooltip content (if any) and a hint that with “Shift” you are able to get more help displayed.

For more details read the merge request.

A video is better than words ;=)

Felix provided a short video in the merge request to demonstrate the basic usage (based on the first implementation, some minor details got fine-tuned later).

Why is that great?

With this feature the “What’s This?” help is a lot easier to discover. It actually makes now more sense then ever to provide it for more complex actions in your applications, people will really be able to find it.

And even nicer, as this is now done in KXMLGUI, all applications using this KDE Framework will automatically provide the new feature, like e.g. Kate ;=)

Comments?

A matching thread for this can be found here on r/KDE.

Saturday, 12 June 2021

Tok's welcome screen

Bon venon! Herzlich willkommen! o kama pona! Tok now has an improved welcoming sequence with improved visuals and user experience.

Tok's password screen

If you want to see this for yourself, you can now log out using the new log out menu option.

Tok's log out menu

Once you're back in Tok, why not try out the new resizable chat list?

Tok's chat list

Or the toggleable sidebar?

Tok's sidebar

Photo and Video Sidebar

Tok now lets you view photos and videos that have been sent in a chat in the sidebar.

tok photo sidebar

tok video sidebar

Deletion

You might realise with the help of the sidebar that there's something inappropriate in your chat.

Now, Tok will allow you to properly delete others' messages if you have the correct permissions.

tok deletion

You can also now choose to only delete messages for yourself in groups/chats where this is applicable.

tok deletion dialog

Mentions

There's a good chance you may want to scold someone for sending an inappropriate photo in your chat. Tok now provides autocompletion for mentions, making it faster to type out someone's username.

tok mentioning

GIFs

Tok now supports displaying GIFs in chat.

Optimisations

Tok has seen a lot of optimisations. Memory usage should now hover around or below the 100MB range idle, depending on how many photos, videos, GIFs, etc. are on your screen.

Bugfixes

A lot of Tok's crashes have been fixed, especially ones pertaining to images.

Removed unneeded drums.

Obtaining Tok

Tok can be built from source from https://invent.kde.org/network/tok.

The support/development room is at https://t.me/kdetok.

Contributing

Interested in contributing? Come on by the dev chat and say hello.

Tags: #libre

In just six days, on Friday next week, KDE Akademy will start, bringing us eight days packed with presentations, workshops, meetings, BoFs and hanging out with friends.

I'm going to Akademy 2021

Talks

I have two talks in the main program. The first one will be about the indoor map display component we built for KDE Itinerary, showing how this works as well as looking at what we can build on top of it.

The second talk is about the work on KDE’s Android release pipeline which I have written about here recently, looking at how this integrates with Craft and Binary Factory, as well as how this plays into KDE’s All About The Apps goal.

BoFs

The bulk of Akademy aren’t talks but a large number of BoFs, workshops and meetings. I’m particularly looking forward to the following topics:

  • KF6: During the last KF6 Sprint we decided to review and revisit the KF6 timeline and branching strategy at Akademy.

  • Application releases and the deployment pipeline: That’s a sub-aspect of the All About The Apps goal, and ties in with my recent work on that for Android releases.

  • Energy efficient software: This includes analyzing, optimizing and continuously monitoring the energy consumption of applications, as well as things like the “Blauer Engel” eco certification.

And judging from past Akademys there will be a whole lot more interesting stuff that I don’t even have on the radar yet :)

Other Topics

Besides the scheduled sessions there’s of course also the hallway track. I’m hoping to gather some feedback for the ongoing work on API for looking up and localizing countries, country subdivisions and timezones there.

Usually I would also use that opportunity to chase people for travel document samples for KDE Itinerary’s data extractor, but that will unfortunately have to wait another year again.

See you at Akademy!

The best part of Akademy however is meeting old and new friends, even if just virtual. I’ll be around the entire time and hope to see many of you there!

If you haven’t done so, it’s not too late to sign up.

This week Plasma 5.22 was released! Overall our focus on stability has paid off, and so far there are no major regressions reported; only a few medium-severity ones which have all already been fixed in Plasma 5.22.1 :). You can read the release announcement, or check out KDE developer Niccolò Venerandi’s lovely video about it:

But something much bigger happened as well: the next phase of the Breeze Evolution initiative was merged, providing a new style for buttons, menu items, checkboxes, radio buttons, sliders, and more! It’s beautiful:

This new style also fixes several bugs plaguing the old styling for these controls, such as sliders becoming invisible when located on a selected list item, and the “default button” of a dialog not being very visually obvious.

The work was implemented by Jan Blackquill in accordance with mockups made by Manuel Jesus de la Fuente and other members of the KDE VDG. It will make its debut in Plasma 5.23. There is a lot of time left to tweak the final appearance as needed, but overall I think it’s really nice and I hope you’re as excited about it as I am!

New Features

Thumbnails for files inside encrypted locations such as Plasma vaults are now generated and displayed as expected, but not saved, to eliminate a data leak (Marcin Gurtowski, Dolphin 21.08)

Kate’s LSP support now extends to the Dart programming language (Waqar Ahmed, Kate 21.08)

Konsole now supports the DECSET 1003 standard, which means that the features in terminal software such as vim which rely on mouse tracking now work (Luis Javier Merino Morán, Konsole 21.08)

This is not strictly speaking a KDE project, but it certainly affects us: the SDDM login manager can now be run as Wayland-native software without needing X11 at all! (Aleix Pol Gonzalez, SDDM 0.20)

Bugfixes & Performance Improvements

Konsole now correctly processes double right-click events (Luis Javier Merino Morán, Konsole 21.08)

emacs xterm-mouse-mode now works in Konsole (Luis Javier Merino Morán, Konsole 21.08)

In the Plasma Wayland Session, additional screens are now detected when using a multi-GPU setup (Xaver Hugl, Plasma 5.22.1)

The Network Speed widget once again works (David Redondo, Plasma 5.22.1)

The weather widget’s BBC weather data source now works again (they changed the API and we had to react to it) (Joe Dight, Plasma 5.22.1)

In the Plasma Wayland session, the transparent background beind Task Switchers is now always blurred as expected (Vlad Zahorodnii, Plasma 5.22.1)

In the Plasma System Monitor app, the “Get New Pages” view now opens in a nice overlay rather than a narrow squeezy column (Dan Leinir Turthra Jensen, Plasma 5.22.1)

Custom shortcuts for “Walk through applications” (bound to Alt+` by default) now work (Andrew Butirsky, Plasma 5.22.1)

The System Settings File Search page no longer sometimes shows a weird duplicated header (Marco Martin, Plasma 5.22.1)

The re-done Autostart page in System Settings now always shows the correct icon for applications set to run at login (Nicolas Fella, Plasma 5.22.1)

When using a lot of window rules, the System Settings Window Rules page is now much faster to load and display them (Ismael Asensio, Plasma 5.23)

Disabled pages in System Monitor no longer temporarily become un-disabled if its sidebar is collapsed into icons-only mode (Arjen Hiemstra, Frameworks 5.84)

Other User Interface Improvements

When entering Party Mode in Elisa, the playlist now automatically scrolls to the currently-playing song if it would otherwise be out of view, and also automatically scrolls the view as needed to make sure that any newly-playing songs remain in view (Tranter Madi, Elisa 21.08)

The “Get New [thing]” dialogs now handle situations like the server being down or slow more gracefully (Dan Leinir Turthra Jensen, Frameworks 5.84):

QtQuick apps using the Kirigami OverlaySheet component no longer let the sheet touch the edges of the window or screen (Devin Lin, Frameworks 5.84)

…And everything else

Keep in mind that this blog only covers the tip of the iceberg! Tons of KDE apps whose development I don’t have time to follow aren’t represented here, and I also don’t mention backend refactoring, improved test coverage, and other changes that are generally not user-facing. If you’re hungry for more, check out https://planet.kde.org/, where you can find blog posts by other KDE contributors detailing the work they’re doing.

How You Can Help

Have a look at https://community.kde.org/Get_Involved to discover ways to be part of a project that really matters. Each contributor makes a huge difference in KDE; you are not a number or a cog in a machine! You don’t have to already be a programmer, either. I wasn’t when I got started. Try it, you’ll like it! We don’t bite!

Finally, consider making a tax-deductible donation to the KDE e.V. foundation.