Blog / Post

What I’ve been pushing to the UBports GitLab lately

A roundup of my recent UBports GitLab merge requests, grouped by theme: the big Mir 2 display-stack port, the Volla-sponsored powersave and battery work, charge limiting, small device niceties, and boot/build plumbing.

00 Preamble

I don’t write much about the upstream side of my Volla work most of what ends up on this blog is the device-specific stuff, the kernels and the boot delays and the things that make a tablet feel like a tablet. But a lot of the work doesn’t live in a device tree. It lives in merge requests against the UBports GitLab, where it gets argued over, reworked, sometimes merged, sometimes closed, and occasionally just sits there waiting for a reviewer to wake up.

So this one is a little tour of what I’ve had open lately. Less of a how-to, more of a “here’s where my time went”. I’ll group it by theme and I’ll be honest about status “merged” only means merged, everything else is open or still under review. All of this is the state of things as of mid-June 2026, and on a fast-moving project states can change under you between the time I write this and the time you read it…

A small note on links: UBports core repos live under gitlab.com/ubports/development/core/<repo>, but a few of these sit in packaging/, hybris-support/ or the ayatana-indicators group, and the kernels are porting repos of their own. Rather than hand you a confidently wrong URL, I’ll just refer to things as repo!NN (e.g. repowerd!101) and trust you to find them.

01 The big one: porting the display stack to Mir 2

This is the meaty one, so it goes first. Ubuntu Touch is moving to 26.04 / Resolute Racon, and the plan is to move forward on historical technical debt. The whole display stack has to come up onto Mir 2.x and its MirAL idiom. That’s not a tidy little patch it’s surgery on the thing that draws every pixel on the screen, and if you get it wrong nothing boots to a UI at all.

The core of it is lomiri-system-compositor !39“Port to mir 2.x MirAL”. The system compositor used to be written by subclassing mir::Server directly; the modern way is the MirAL idiom, so it had to be essentially rewritten around that. Still open, because it’s the keystone and I’d rather it be right than fast.

Underneath that sits the graphics platform. mir-android2-platform !20 ports the Halium / libhybris android platform to the Mir 2.25 buffer API — which also drags the whole thing into C++20. This one I didn’t start from scratch: it builds on NotKit / TheKit’s earlier mir2 branch, so a lot of credit there is theirs. Also open.

Then there’s the chain of consumers that have to stop talking to the old Mir client library:

  • lomiri-app-launch !79 — drop mirclient, switch to the Wayland QPA, and disable client-side decorations. Open.
  • qtmir !107“Fix synchronization bug with Mir2 crashing Mir”. This one is merged, which is a nice feeling because a sync bug that crashes the compositor is exactly the kind of thing that makes the whole port look broken when it isn’t.
  • qtmir !90 (still a draft) — fixing the cursor position after a screen rotation, the classic mapFromGlobal story where the pointer lands somewhere that made sense before you turned the screen.
  • qtmir !114 — use Qt::AutoConnection instead of Qt::DirectConnection, a threading fix. Open.

And the boring-but-necessary plumbing: packaging/mir2 !2, “Sync mir 2.25.2-5 from Debian unstable” — because before you can port onto Mir 2.25 you need Mir 2.25 actually packaged for us. Open. (sunweaver has been the one keeping the qtmir packaging sane through all this credit where it’s due.)

One thing I’m a little proud of: a couple of the bugs I hit weren’t ours, they were in Mir itself, in the path where Mir runs nested under another compositor. So I sent them upstream canonical/mir PR #5020, two fixes: fall back to the output-mode size when we get a zero-size toplevel_configure, and retry poll() on EINTR instead of just dying. Open upstream at the time of writing. It’s always a good sign when a downstream port starts handing fixes back up the river.

02 Power saving and battery

The heart of it is repowerd !101: a proper CPU power-save service plus a battery-saver mode. It exposes a new com.lomiri.Repowerd.PowerSaver interface, it can cap the per-cluster maximum CPU frequency (through the MediaTek PPM, or just scaling_max_freq), and it implements a Samsung-style battery saver — drop 5G down to LTE, knock 10% off the brightness, shorten the sleep timeouts. Nothing exotic, all the boring levers that together buy you real minutes. Open; mariogrip has been reviewing it.

A service nobody can toggle is useless, so it needed UI on both ends:

  • lomiri-system-settings !546 — the battery-saver section in the Battery panel, wired to that PowerSaver service. Open; peat-psuwit is reviewing.
  • ayatana-indicator-power !12 — a battery-saver toggle right in the power indicator, driven by the same repowerd PowerSaver, and politely hidden when the service isn’t there at all. There’s an upstream form of it too: AyatanaIndicators/ayatana-indicator-power PR #99, because the indicator isn’t only ours and it’s rude to fork what you can upstream.

Not everything landed the way I first drew it, and I’d rather say so. deviceinfo !38 was going to store the power-save profiles the per-device LITTLE / BIG max-kHz numbers inside the device configs. In the end I closed it: the reviewer steered the profiles to live somewhere else, and once that decision was made the MR didn’t have a reason to exist anymore. That’s not a failure, that’s just review doing its job, but it’s an honest part of the picture.

There’s also repowerd !103 (a draft) an Android 10+ SystemSuspend wakelock backend, closing #43. Still cooking.

03 Charge limiting — stop at 80, live longer

Related but its own thing: capping the charge at around 80–85% so the cell doesn’t sit at a constant 100% and die young. lomiri-system-settings !506 “Add battery charge limiter for mediatek devices” is the UI for that on MTK hardware. It’s open and it’s had a long review; peat-psuwit drove most of the design discussion, and honestly the back-and-forth made it better.

The interesting part is the journey of the enforcement logic. It started life as a little standalone chargepolicyd proof-of-concept a daemon whose whole job was to watch the battery and stop the charge and the conclusion of the review was that a separate daemon was the wrong shape for it. So it got folded into repowerd as a proper service instead. Less code to babysit, one fewer thing in the process list, and it sits next to the power-save logic where it belongs.

04 Small device niceties

Not everything has to be a saga. Some MRs are just small quality-of-life things that I wanted on my own devices and figured others would too.

  • lomiri !160“Add reboot to recovery option”, a “Restart to Recovery” entry in the power dialog. It’s gated twice: behind a deviceinfo capability so it only shows where it actually works, and behind a GSettings opt-in so it’s not in your face by default. Open.
  • deviceinfo !37 — the other half of that: it adds the capability keys supportsRebootToRecovery (the one !160 keys off) and supportsMultiColorLed. Open.
  • seeds/ubuntu-touch !137 — add python3-evdev to the seed, for the Touchpad gesture service that needs it. Tiny, one-line in spirit, and merged. Sometimes the small ones go through clean and it’s lovely.

05 Boot and build plumbing

Last theme, the least glamorous and the closest to the metal the porting tools and the device kernels. Nobody cheers for build plumbing, but it’s where a lot of the “why does this device take eight seconds to show a logo” answers actually live.

On the generic side, in halium-generic-adaptation-build-tools:

  • !69 “dtbo: add dtbo_ids”merged.
  • !72 “make-bootimage.sh: extract ramdisk before copy and compress” — open.

And the device kernels themselves:

  • android_kernel_volla_mt8781 !6 — dts fixes for boot delays and TCPC conflicts. Open. (This is the one chasing those slow-boot seconds.)
  • android_kernel_volla_mt8781 !4 — hybrid touchscreen support. Merged.
  • kernel-volla-mt6877 (the Quintus) !7 — boot-speed optimizations. Open.
  • kernel-volla-mt6877 !6 — a USB gadget scatter-gather fix. Merged.

06 So what’s next?

The honest answer is: land the Mir 2 port. Everything in section 1 is the difference between Ubuntu Touch having a better future or not, and we are this close. The powersave and charge-limiter work is the stuff I most want in users’ hands, and that one is mostly a matter of grinding through review.

If you want to actually watch any of this happen, it’s all out in the open on the UBports GitLab come read the diffs, come argue with me in the threads, that’s the whole point of doing it upstream.

Special thanks

Special thanks to peat-psuwit (Ratchanan Srirattanamet) for driving the charge-limiter design and reviewing half of this, to NotKit / TheKit whose mir2 work the android2 platform is built on, to mariogrip for reviewing the powersave set, and to sunweaver for keeping the qtmir packaging together. And to Volla / Volla Systeme GmbH, the reason I got to do it properly instead of in stolen evenings.

Sources & further reading

  • The UBports GitLab — gitlab.com/ubports (the MRs above, by repo!NN)
  • Mir upstream — canonical/mir PR #5020
  • Ayatana Indicators — AyatanaIndicators/ayatana-indicator-power PR #99

Enjoyed this? This work is patron-funded. Patrons get early builds, devlogs, and a vote on which device I attack next — everything I make stays free and open.