Janet's Shenanigans

This response from a reader pretty much sums it up:

Stop One: Will Craft Work?

Craft seems like the no-brainer for a KDE project to use, considering it's in-house and supports all of KDE's frameworks and has packaging capabilities. Unfortunately, what sounds good on paper does not translate to what's good in execution.

When I first checked it out, the current version being shipped had incorrect code that would have failed to compile had it been a compiled language instead of Python. Heck, even running a typechecker for Python would have revealed the fact that the code was trying to call a function that didn't exist. Yet, this managed to get shipped. Not a good first impression.

After manually patching in the function into existence on my system, I ran into another hurdle: Craft's env script is broken; mangling PATH to an extent where entries like /usr/bin and /bin and other things got just truncated into oblivion, resulting in a shell where you couldn't do much of anything.

After manually patching PATH to be not mangled, I ran into another and the final hurdle before I gave up: Craft tried to use half bundled libraries and tools and half system libraries and tools, resulting in dynamic linker errors from system tools not finding symbols they needed from bundled libraries.

When I brought these issues up in the Craft chat, the answers basically amounted to a lack of care and “go use Ubuntu.” Not acceptable for Tok considering most of the people interested in building Tok like this don't use Ubuntu, and honestly doesn't make you have much faith in a system for porting utilities to other platforms if said system doesn't even work across the distributions of one platform.

Stop Two: Maybe Conan?

Conan seems like the second-in-line no-brainer for Tok to use. It's the largest C++ package manager, even supporting Qbs. Of course, like with Craft, what sounds good on paper doesn't match up to execution.

Out of the gate, I looked at the Qt package, only to find that there was one (1) Qt package for it consisting of the entirety of Qt, WebEngne and all. Kinda oof, but not a deal breaker. Well, it wouldn't be a dealbreaker if Conan had prebuilt Qt packages for

- Settings: arch=x86_64, build_type=Release, compiler=gcc, compiler.version=11, os=Linux

But it doesn't. I'm not going to build an entire web engine just for an attempt at maybe getting a non-Linux build of Tok, and having to build a web engine as part of Tok's CI is a no-go in terms of disk, memory and CPU.

Stop Three: vcpkg

Considering Microsoft has been a developer tools company for about as thrice as long as I've been alive, I hope their take at the C++ package manager thing is worth their salt.

Some weirdness ensued with the VCPKG_ROOT environment variable at first, but it was easy to fix by pointing it at the vcpkg repo.

While doing the vcpkg install, I found the output somewhat hard to follow, so I had no idea how far along it was. I just let it sit since it seemed to be making progress.

While vcpkg didn't have prebuilt binaries for my setup, it didn't require building all Qt modules like Conan did, so the ask was much more reasonable.

And then I noticed a big issue: vcpkg has absolutely zero versioning, other than the git repository with all the package manifests. This essentially means that in order to build with Qt5, I need to commit to an ancient version of vcpkg packages and stay there indefinitely. I also have to ask users to roll back their vcpkg install that far to build Tok. Not really acceptable as an ask for people who might want to build Tok for not Linux.

Stop Four: Wait, It's Already Here (At Least For Macs)

Turns out Nix, the thing that Tok already supports building with, also supports macOS. Well, that was easy. While it doesn't spit out a premade .app like other porting systems can do, it does ensure a working build with available dependencies, which is already most of the way there.

Conclusion: Apples And Penguins Rule, Everyone Else Drools

Cross-platform C++ packaging and distribution is still a very unsolved problem, unlike other languages/frameworks like Go, Electron, Rust, Zig, etc. as I learned painfully through these escapades. Nix seems the most promising on this front, as it offers a very consistent environment across platforms, which gets you most of the way there in terms of building. It doesn't support Windows (yet?), but simply being able to use a functional compiler instead of Apple Clang is already a killer feature for using it to port apps to macOS.

Qbs is also a huge help in terms of the porting process, as it natively supports building things that require auxiliary scripts or build system hacks with other build systems, like .app bundles, Windows installers, and multi-architecture .app/.aab/.apks with just a few or no extra lines in the build system.

For Tok on macOS, all I need to do is add these two lines to the build script in order to get a usable .app file from it:

Depends { name: "bundle" }
bundle.isBundle: true

While it lacks a lot of metadata that you need to fill in yourself, it's again, another 99% of the way there solution where the remaining 1% is mostly just a little data or boilerplate or running a tool.

I still haven't figured out what I'll be doing for Windows, but the need for an end-user Windows package is still a long ways off, considering Tok is still nowhere near a 1.0 release status. Perhaps I can make leverage of Fedora's mingw packages or check out https://mxe.cc/, or maybe just install dependencies from source to a Windows system without a package manager, and bundle them during the build process. If you have any suggestions, do feel free to hop on by in the Tok chat and drop them.

Tags: #libre

This is the sequel post to the previous post on hRPC; after hRPC has matured and gotten an actual spec written for it. This post contains much more information about hRPC, as well as tutorials for it.

This post is mirrored from our post on dev.to

Co-authored by: Yusuf Bera Ertan (yusdacra@GitHub), Danil Korennykh (Bluskript@GitHub), Janet Blackquill (pontaoski@GitHub)

hRPC is a new RPC system that we, at Harmony, have been developing and using for our decentralized chat protocol. It uses Protocol Buffers (Protobufs) as a wire format, and supports streaming.

hRPC is primarily made for user-facing APIs and aims to be as simple to use as possible.

If you would like to learn more, the hRPC specification can be found here.

What is an RPC system? If you know traditional API models like REST, then you can think of RPC as a more integrated version of that. Instead of defining requests by endpoint and method, requests are defined as methods on objects or services. With good code generation, an RPC system is often easier and safer to use for both clients and servers.

Why hRPC?

hRPC uses REST to model plain unary requests, and WebSockets to model streaming requests. As such, it should be easy to write a library for the languages that don't already support it.

hRPC features:

  • Type safety
  • Strict protocol conformance on both ends
  • Easy streaming logic
  • More elegant server and client code with interfaces/traits and endpoint generation.
  • Cross-language code generation
  • Smaller request sizes
  • Faster request parsing

Why Not Twirp?

Twirp and hRPC have a lot in common, but the key difference that makes Twirp a dealbreaker for harmony is its lack of support for streaming RPCs. Harmony's vision was to represent all endpoints in Protobuf format, and as a result Twirp became fundamentally incompatible.

Why Not gRPC?

gRPC is the de-facto RPC system, in fact protobuf and gRPC come together a lot of the time. So the question is, why would you want to use hRPC instead?

Unfortunately, gRPC has many limitations, and most of them result from its low-level nature.

The lack of web support

At Harmony, support for web-based clients was a must, as was keeping things simple to implement. gRPC had neither. As stated by gRPC: > It is currently impossible to implement the HTTP/2 gRPC spec in the browser, as there is simply no browser API with enough fine-grained control over the requests.

The gRPC slowloris

gRPC streams are essentially just a long-running HTTP request. Whenever data needs to be sent, it just sends a new HTTP/2 frame. The issue with this, however, is that most reverse proxies do not understand gRPC streaming. At Harmony, it was fairly common for sockets to disconnect because they are idle for long stretches of time. NGINX and other reverse proxies would see these idle connections, and would close them, causing issues to all of our clients. hRPC's use of WebSockets solves this use-case, as reverse proxies are fully capable to understand them.

In general, with hRPC we retain the bulk of gRPC's advantages while simplifying stuff massively.

Why not plain REST?

Protobuf provides a more compact binary format for requests than JSON. It lets the user to define a schema for their messages and RPCs which results in easy server and client code generation. Protobuf also has features that are very useful for these kind of schemas (such as extensions), and as such is a nice fit for hRPC.

A Simple Chat Example

Let's try out hRPC with a basic chat example. This is a simple system that supports posting chat messages which are then streamed back to all clients. Here is the protocol:

syntax = "proto3";

package chat;

// Empty object which is used in place of nothing
message Empty { }

// Object that represents a chat message
message Message { string content = 1; }

service Chat {
  // Endpoint to send a chat message
  rpc SendMessage(Message) returns (Empty);
  // Endpoint to stream chat messages
  rpc StreamMessages(Empty) returns (stream Message);

By the end, this is what we will have:

vue client being demonstrated

Getting Started

NOTE: If you don't want to follow along, you can find the full server example at hRPC examples repository.

Let's start by writing a server that implements this. We will use hrpc-rs, which is a Rust implementation of hRPC.

Note: If you don't have Rust installed, you can install it from the rustup website.

We get started with creating our project with cargo new chat-example --bin.

Now we will need to add a few dependencies to Cargo.toml:

# `hrpc-build` will handle generating Protobuf code for us
# The features we enable here matches the ones we enable for `hrpc`
hrpc-build = { version = "0.29", features = ["server", "recommended"] }

# `prost` provides us with protobuf decoding and encoding
prost = "0.9"
# `hrpc` is the `hrpc-rs` main crate!
# Enable hrpc's server features, and the recommended transport
hrpc = { version = "0.29", features = ["server", "recommended"] }
# `tokio` is the async runtime we use
# Enable tokio's macros so we can mark our main function, and enable multi
# threaded runtime
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
# `tower-http` is a collection of HTTP related middleware
tower-http = { version = "0.1", features = ["cors"] }
# Logging utilities
# `tracing` gives us the ability to log from anywhere we want
tracing = "0.1"
# `tracing-subscriber` gives us a terminal logger
tracing-subscriber = "0.3"

Don't forget to check if your project compiles with cargo check!

Building the Protobufs

Now, let's get basic protobuf code generation working.

First, go ahead and copy the chat protocol from earlier into src/chat.proto.

After that we will need a build script. Make a file called build.rs in the root of the project:

// build.rs
fn main() {
    // The path here is the path to our protocol file
    // which we copied in the previous step!
    // This will generate Rust code for our protobuf definitions.
        .expect("could not compile the proto");

And lastly, we need to import the generated code:

// src/main.rs
// Our chat package generated code
pub mod chat {
    // This imports all the generated code for you

// This is empty for now!
fn main() { }

Now you can run cargo check to see if it compiles!

Implementing the Protocol

In this section, we will implement the protocol endpoints.

First, get started by importing the stuff we will need:

// src/main.rs
// top of the file

// Import everything from chat package, and the generated
// server trait
use chat::{*, chat_server::*};
// Import the server prelude, which contains
// often used code that is used to develop servers.
use hrpc::server::prelude::*;

Now, let's define the business logic for the Chat server. This is a simple example, so we can just use channels from tokio::sync::broadcast. This will allow us to broadcast our chat messages to all clients connected.

// ... other `use` statements

// The channel we will use to broadcast our chat messages
use tokio::sync::broadcast;

Afterwards we can define our service state:

pub struct ChatService {
    // The sender half of our broadcast channel.
    // We will use it's `.subscribe()` method to get a
    // receiver when a client connects.
    message_broadcast: broadcast::Sender<Message>,

Then we define a simple constructor:

impl ChatService {
    // Creates a new `ChatService`
    fn new() -> Self {
        // Create a broadcast channel with a maximum 100
        // amount of items that can be pending. This
        // doesn't matter in our case, so the number is
        // arbitrary.
        let (tx, _) = broadcast::channel(100);
        Self {
            message_broadcast: tx,

Now we need to implement the generated trait for our service:

impl Chat for ChatService {
    // This corresponds to the SendMessage endpoint
    // `handler` is a Rust macro that is used to transform
    // an `async fn` into a properly typed hRPC trait method.
    async fn send_message(&self, request: Request<Message>) -> ServerResult<Response<Empty>> {
        // we will add this in a bit
    // This corresponds to the StreamMessages endpoint
    async fn stream_messages(
        // We don't use the request here, so we can just ignore it.
        // The leading `_` stops Rust from complaining about unused
        // variables!
        _request: Request<()>,
        socket: Socket<Message, Empty>,
    ) -> ServerResult<()> {
        // we will add this in a bit

And now for the actual logic, let's start with message sending:

async fn send_message(&self, request: Request<Message>) -> ServerResult<Response<Empty>> {
    // Extract the chat message from the request
    let message = request.into_message().await?;

    // Try to broadcast the chat message across the channel
    // if it fails return an error
    if self.message_broadcast.send(message).is_err() {
        return Err(HrpcError::new_internal_server_error("couldn't broadcast message"));
    // Log the message we just got
    tracing::info!("got message: {}", message.content);

    Ok((Empty {}).into_response())

Streaming logic is simple. Simply subscribe to the broadcast channel, and then read messages from that channel forever until there's an error:

async fn stream_messages(
    _request: Request<()>,
    socket: Socket<Message, Empty>,
) -> ServerResult<()> {
    // Subscribe to the message broadcaster
    let mut message_receiver = self.message_broadcast.subscribe();

    // Poll for received messages...
    while let Ok(message) = message_receiver.recv().await {
        // ...and send them to client.


Let's put all of this together in the main function. We'll make a new chat server, where we pass in our implementation of the service. We'll be serving using the Hyper HTTP transport for the server, although this can be swapped out with another transport if needed.

// ...other imports

// Import our CORS middleware
use tower_http::cors::CorsLayer;

// Import the Hyper HTTP transport for hRPC
use hrpc::server::transport::http::Hyper;

// `tokio::main` is a Rust macro that converts an `async fn`
// `main` function into a synchronous `main` function, and enables
// you to use the `tokio` async runtime. The runtime we use is the
// multithreaded runtime, which is what we want.
async fn main() -> Result<(), BoxError> {
    // Initialize the default logging in `tracing-subscriber`
    // which is logging to the terminal
    // Create our chat service
    let service = ChatServer::new(ChatService::new());

    // Create our transport that we will use to serve our service
    let transport = Hyper::new("")?;

    // Layer our transport for use with CORS.
    // Since this is specific to HTTP, we use the transport's layer method.
    // Note: A "layer" can simply be thought of as a middleware!
    let transport = transport.layer(CorsLayer::permissive());

    // Serve our service with our transport


Notice how in the code above, we needed to specify a CORS layer. The next step of the process, of course, is to write a frontend for this.

Frontend (CLI)

If you don't want to use the web client example, you can try the CLI client at hRPC examples repository. Keep in mind that this post doesn't cover writing a CLI client.

To run it, after you git clone the repository linked, navigate to chat/tui-client and run cargo run. Instructions also available in the READMEs in the repository.

Frontend (Vue 3 + Vite + TS)

NOTE: If you don't want to follow along, you can find the full web client example at hRPC examples repository.

The setup is a basic Vite project using the Vue template, with all of the boilerplate demo code removed. Once you have the project made, install the following packages:

npm i @protobuf-ts/runtime @protobuf-ts/runtime-rpc @harmony-dev/transport-hrpc

npm i -D @protobuf-ts/plugin @protobuf-ts/protoc windicss vite-plugin-windicss

In order to get Protobuf generation working, we'll use Buf, a tool specifically built for building protocol buffers. Start by making the following buf.gen.yaml:

version: v1
  - name: ts
    out: gen
    opt: generate_dependencies,long_type_string
    path: ./node_modules/@protobuf-ts/plugin/bin/protoc-gen-ts

The config above invokes the code generator we installed, and enables a string representation for longs, and generating code for builtin google types too.

Now, paste the protocol from earlier into protocol/chat.proto in the root of the folder, and run buf generate ./protocol. If you see a gen folder appear, then the code generation worked! ✅

The Implementation

When building the UI, it's useful to have a live preview of our site. Run npm run dev in terminal which will start a new dev server.

The entire implementation will be done in src/App.vue, the main Vue component for the site.

For the business logic, we'll be using the new fancy and shiny Vue 3 script setup syntax. Start by defining it:

<script setup lang="ts">

Now, inside this block, we first create a chat client by passing our client configuration into the HrpcTransport constructor:

import { ChatClient } from "../gen/chat.client";
import { HrpcTransport } from "@harmony-dev/transport-hrpc";

const client = new ChatClient(
  new HrpcTransport({
    baseUrl: "",
    insecure: true

Next, we will define a reactive list of messages, and content of the text input:

const content = ref("");
const msgs = reactive<string[]>([]);

These refs are used in the UI, and these are what we'll ultimately need to use in order to reflect a change.

Now let's add our API logic:

// when the component mounts (page loads)
onMounted(() => {
  // start streaming messages
  client.streamMessages({}).responses.onMessage((msg) => {
    // add the message to the list

// keyboard handler for the input
const onKey = (ev: KeyboardEvent) => {
  if (ev.key !== "Enter") return; // only send a message on enter
    content: content.value,
  }); // send a message to the server
  content.value = ""; // clear the textbox later

Now let's add some layouting and styling, with registered event handlers for the input and a v-for loop to display the messages:

  <div class="h-100vh w-100vw bg-surface-900 flex flex-col justify-center p-3">
    <div class="flex-1 p-3 flex flex-col gap-2 overflow-auto">
      <p class="p-3 max-w-30ch rounded-md bg-surface-800" v-for="m in msgs" :key="m">{{ m }}</p>
        focus:outline-none focus:ring-3

If you are unsure what these classes mean, take a look at WindiCSS to learn more.

And with that we complete our chat application!

Other Implementations

While we used Rust for server and TypeScript for client here, hRPC is cross-language. The harmony-development organisation on GitHub has other implementations, most located in the hRPC repo.

Tags: #libre #harmony

kate showing a completion for qml

kate showing another completion for qml

qml-lsp is a simple LSP server for QML. it currently only offers primitive non-context-aware completions, without anything else. it understands:

  • completing component names
  • completing properties defined in surrounding component (not properties from its superclasses)
  • completing enums

note that it doesn't understand project-local QML components, only stuff installed to the system QML dirs with qmlplugindump set up correctly.

regardless, it does a decent job at completing system types, and considering that Qt Creator struggles with some of the plugins that qml-lsp has no problem with, it's pretty usable and an improvement over the nothing found in editors other than Qt Creator.

you can check out the source at https://invent.kde.org/cblack/qew-em-el-el-ess-pee, or fetch a statically linked binary here. plonk the binary in your PATH under the name qml-lsp.

kate LSP config:

	"qml": {
		"command": ["qml-lsp"],
		"highlightingModeRegex": "^QML$"

tags: #libre

the messages view showing a file thumbnail

Tok has seen a handful of improvements this week, one being that file messages now show a thumbnail if available.

Music View

music view

Tok can now show you all songs that have been uploaded to a chat.

Hop To Message

Tok now allows you to hop to a message by tapping on it, no matter how far back in history it is.


  • messages with newlines can be sent
  • formatting works again
  • nix files updated
  • the message text field no longer keeps the last formatting applied when you send a message

Obtaining Tok

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

There's a Telegram room for Tok available at https://t.me/kdetok, where you can come on and chat about anything Tok related, such as asking questions on using or building Tok.


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

Tags: #libre #tok

syntax highlighting being demonstrated in tok

Tok has had many changes since the last time I made one of these blog posts, the biggest one being that code blocks are syntax highlighted!

Tok uses KSyntaxHighlighting, the same syntax highlighting engine that powers Kate, KWrite, and other KDE applications that feature syntax highlighting.

Additionally, messages containing codeblocks are able to grow horizontally in width beyond the usual message size, letting you read horizontally wide code easier.

Emoji Completion

emoji completion

Tok now displays autocompletion for :emojis:, making the process of typing in emojis much more seamless.

Edited Indicator

edited indicator

Tok now indicates when a message has been edited by the sender.

Jump To Start Buttons

jump to start

Tok now has buttons that allow you to hop back to the start of various views, such as the chats list and the messages view.

Improved In-Window Menubar

better in-window menubar, featuring visual changes

Tok's in-window menubar now has various improvements, such as using the colour of the rest of the header area, as well as the right sidebar respecting the menubar's appearance.


proxy support

Tok now supports configuring proxies, allowing you to access Telegram in countries that don't want you to access Telegram.

And in true anticonvergent fashion, Tok has a dedicated mobile UI for proxies instead of simply using the desktop UI on mobile or the mobile UI on desktop.

proxies on mobile

Better Notifications

better notifications

Tok notifications now display more information about the message, support more message types, and display the profile picture of the chat you're receiving the notification from.


Tok now renders mentions as links that display the user's profile when clicked.

Minor UI Improvements

Chat list unread indicators now become pill-shaped whenever they grow horizontally.

The typing indicator in the chat list is now accent-coloured, like the typing indicator in the header.

Translation Fixes

Tok now correctly loads translation files, allowing it to render in non-English languages.


Tok's startup time has been optimised by a few hundred milliseconds.


Tok no longer resets the scroll position of the chats list whenever chats are moved.

Alt-Up, Alt-Down, and Ctrl-K keyboard shortcuts work again.

Formatting is generally less buggier, and message formats better match how they're supposed to look.

Obtaining Tok

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

There's a Telegram room for Tok available at https://t.me/kdetok, where you can come on and chat about anything Tok related, such as asking questions on using or building Tok.


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

Tags: #libre

This blog post goes over the set of tools you have to work with when doing QtQuick UIs that need to perform something asynchronously.

Data Reactivity

Probably the oldest tool in the toolbox, as it's foundational to QML's construction, and one you should already know how to use.

BooksModel {
    id: booksModel
Button {
    text: `The model has ${booksModel.count} items`
Button {
   onClicked: booksModel.refresh()

QML is reactive, meaning that when you change a property, dependent bindings update automatically.

When applied to asynchrony, this means that asynchronous events or requests can simply mutate an object's properties on completion, and the UI will change when it does.

This is the most common form of asynchrony in a QtQuick UI, and it should be your first goto when you need to perform something asynchronously and display results in the UI, as it plays nicely with a declarative UI.

This is generally the right pattern to use when you can describe the asynchronous process as “the UI displays the current state of a model/object, which changes in response to events.”


The newest tool in the toolbox (at least in QML), Thennables play nicely with other cool and shiny things, such as C++20 Coroutines.

Button {
    text: "Do it"
    onClicked: getNewButtonTextFromTheInternet().then((it) => this.text = it)

Implementations of the Thennable pattern can be found in Qt Automotive, as the QIviPendingReply family of classes, and in Croutons, as the Future family of classes. These allow you to make an object in C++ that you can return as a Thennable to QML after scheduling a task that'll fufill or reject the thennable from the C++ side. On the backend, it usually looks like this:

QIviPendingReply<bool> foo;

// runs asynchronously
client->getStatus([foo](bool isOnline) mutable {

return foo;

Or, if you're using Croutons + C++20 Coroutines...

auto response = co_await client->getStatus();

co_return response.isOnline();

This is generally the right pattern to use when you can describe the flow of asynchronous actions as an “and then” chain. For example, “make a request and then display a notification to the user if it succeeds or fails”. If the asynchronous process can take advantage of data reactivity, this is probably not the correct pattern to use.

Task Objects

This is rarely the right pattern, and people that have to use your API will probably hate you if you ship this in a library. Despite that, it's a (thankfully uncommon) thing in the wild, so I'll document it here.

FileDownload {
    id: fileDownloader

    url: "http://placehold.it/350x150"

    onStarted: status.text = "Starting download..."
    onError: status.text = "Download failed"
    onProgressChanged: status.text = `Download ${progress}% done`
    onFinished: status.text = "Download finished"
Button {
	text: "Download File"
	onClicked: fileDownloader.start()

This essentially consists of using a full-fledged object description in QML to describe a task, and to connect to signals on it. The object needs to be given an id, and that id needs to be used to invoke a .start() method or setting a running property to true.

Task objects have many disadvantages: – trying to describe an imperative process directly with a declarative language, which goes as well as you'd expect – require creating and binding properties for an extra object, which can add up fast, even if the object's task isn't actually triggered by a user action. e.g., 50 download buttons with corresponding FileDownload objects, and only a few of those buttons will ever get pressed. – non-UI matters in an otherwise UI-only language

You can often recognise task objects by distinct “input” properties and “output” signals (less commonly, “output” properties) for a given task.

Instead of using a task object, considering using a Thennable, or better utilising reactive data.


I've honestly never seen the WorkerScript type get used in an actual program, so I'm not sure what their practical application is. Despite that, these are another option for currency, so I'll describe their behaviour.

A WorkerScript is a wrapper around the JS notion of a WebWorker, which runs in its own thread and receives and sends messages from the main thread. This is the only way to get data in and out of them, so you have to structure it as messages.

Tags: #libre

mobile upload dialogue

Tok now has TWO new completely revamped upload dialogues: one for desktop, and one for mobile. Despite how much of the app is spent using stuff that isn't the upload dialogues, the upload dialogues took a LOT of time and work to get implemented correctly.

The mobile dialogue makes it convenient to browse through your most recent photos, videos, music, and files to share them with your friends.

video upload dialogue on mobile

music upload dialogue on mobile

files upload dialogue on mobile

On desktop, the upload dialogue now offers a preview of your file, and for images, the option to compress it.

photo upload on desktop

video upload on desktop

music upload on desktop

file upload on desktop

Sending State

send state

The sending state of outgoing messages is now displayed with a little icon by the timestamp.

Adjusted Chat List Look

adjusted chat list look

The look of the chat list has been slightly adjusted, to make it look more like other KDE apps. More information is shown, such as the sending status of outgoing messages and the timestamp of the latest message.

Improved Pasting

Tok now has improved pasting capabilities, able to paste from apps that put images on the clipboard directly like Firefox and Spectacle now.

Obtaining Tok

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

There's a Telegram room for Tok available at https://t.me/kdetok, where you can come on and chat about anything Tok related, such as asking questions on using or building Tok.


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

Tags: #libre #tok

Meta: The first thing I have to announce is, that I'm tired of trying to come up with a cool name for every blog post. I'll just use This Week In Tok from now on.

Secret (E2EE) Chats

Tok now supports secret chats, which are indicated in the chat list using a lock icon. Secret chats are Telegram's E2EE solution, and since the clients (like Tok) are open source and the algorithim well-documented, you can easily verify the implementation for yourself. Wish I could say the same for some other E2EE chats.

a secret chat

Color Scheme Persistence

Tok now remembers which colour scheme you picked between restarts.

New Desktop Settings

Tok now has a revamped settings dialogue more in line with other KDE apps on desktop.

desktop settings

New Mobile Settings

Tok now has new dedicated mobile settings that offer a touch-friendly interface.

mobile settings

Creating Chats

Tok now allows you to create chats of all kinds: one-on-one private chats, secret chats, private groups, public groups, and channels.

the dialogue

Disabling Notifications

You can now disable notifications globally in Tok quickly and easily and enable them just as easily.


Image Improvements

Messages with images are now much less jumpy than they were before.

Images also now show a blurry thumbnail while loading.


System Tray

Tok now offers the ability to minimize to the system tray on desktop systems, where you can still receive notifications without a visible Tok window and disable/enable them from the tray directly.

system tray

Obtaining Tok

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

There's a Telegram room for Tok available at https://t.me/kdetok, where you can come on and chat about anything Tok related, such as asking questions on using or building Tok.


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

Tags: #libre #tok

tok's in-window menu bar

The past two weeks in Tok have been relatively slow, focusing on quality of life features & more user experience improvements more than sweeping new features.

Most noticeably, Tok now has an in-window menubar on desktop if you do not have a global menu bar. This provides globally available functionality in a well-organised palette of commands.

Creating Supergroups and Channels

tok's supergroup creation dialog

Tok is now capable of creating supergroups and channels.

Editing Sent Messages

tok editing sent messages

Tok can now edit messages that have been sent.

Better Sizing

tok showing a better-sized sticker

Sizing in Tok has been adjusted to make better use of screen real estate and to look less jumpy when resizing the window. Stickers no longer grow unboundedly and remain at a nice and small size, the way they were intended to be.

tok link preview

Tok can now display a preview of links sent in chat.

Better Desktop Folders Presentation

better folders

Tok now uses a vertical sidebar of folder items instead

Chat List Reordering

Tok now keeps the chat list's order up-to-date, moving chats with new messages to the top of the list and respects you moving pinned chats on other Telegram apps, moving them accordingly without the need to restart.


Removed unneeded drums.

Obtaining Tok

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

There's a Telegram room for Tok available at https://t.me/kdetok, where you can come on and chat about anything Tok related, such as asking questions on using or building Tok.


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

Tags: #libre #tok

global menu

Tok has seen a handful of mostly user-experience focused improvements; the most noticeable one being the presence of global menu support. Tok now supports global menu bars; which are typically at least five times faster to access than window-local menus due to Fitts' Law.

GUI Editing

gui editing

Tok now has GUI editing of messages; allowing you to use a right click menu (or the new global menu) to apply formatting to your message.

Or, if you're so inclined, there's also keyboard shortcut alternatives to apply formatting to your message.

Typing Indicator

typing indicator

Tok now indicates whether the people (or bots) on the other side are typing or uploading an image/video/etc.

Video Download Progress

tok video progress

Tok now indicates how much of a video has downloaded, and how large a downloaded video will be. There is also a progress bar.

Mobile Performance Improvements

As per usual, Tok is always striving to be lightweight. Mobile was the area of focus for this changelog's performance improvements. Opening the group information should now be instantaneous; where it took a few seconds before.

No Left Gap In Channels

no left gap

No stone goes unturned in making Tok as visually polished as possible; even complicating the code in order to remove a few pixels of pointless blank space.

Tok now doesn't display a left gap on messages in channels.


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.


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

Tags: #libre #tok