4
27
Fork
You've already forked Tuba
5

feat: unifiedpush #1690

Merged
GeopJr merged 2 commits from feat/unifiedpush into main 2026年07月14日 06:37:21 +02:00

fix: #42


Uses https://codeberg.org/UnifiedPush/unifiedpush-rs/

I'm not entirely sure about the UX around it, but functionality-wise it works. One deliberate decision was to skip most of the app startup when activated through dbus. That's because it's quite expensive for stuff that's left unused for handling the notification (e.g. reading secrets, network monitor, proxy, settings etc.). So it just does the bare minimum and if activated normally it will go through the proper startup code. I do think I went a bit too far with the compile time flags and might be annoying to maintain now, might have to let it call the separate function in the non-unifiedpush compilation too...


Onto API:

  1. on startup we create the unifiedpush instance and try to register a distributor. There's only 1 on Linux as far as I know, so I didn't bother with a UI yet, if there's more than 1, it will use the first it find, if there's 0, it will set a variable so we know that UP is not available.
  2. if --unifiedpush-bg push is passed, the process will launch as a service and will die after a while (aka after it handles the message in this case). The above startup workflow will run which is basically skipping the normal workflow.
  3. on new endpoint, it unregisters and then registers a webpush instance with mastodon, uses the user's settings for what types to receive and sets unifiedpush_enabled to true for the user if successful
  4. on unregister, it unregisters from the instance and sets unifiedpush_enabled to false
  5. on new message, it gets the account uuid from the instance (it's uuid:fedi_instance) and parses the message which includes {notification_type, icon, title, body, notification_id} among others. If it's running in --unifiedpush-bg, we basically just use the info from the message and send the notification as is, but if it's running in normal mode, we fetch the actual notification object from the ID and then send the notification like we'd normally do with our own titles, bodies, buttons, grouping, etc. That, again, allows this to be very optimized when the app is closed while fully featured when it's open.
  6. For a user to register, they go to settings>notifications, if UnifiedPush + distributor is available there will be a clickable switch, otherwise it won't be clickable and the reason will be shown. Oh, and on registration, we also use the VAPID from the mastodon instance, if it has any.
  7. When the settings window is closed, we also send the muted-notification-types to the webpush api so mastodon only sends us those we want. Some newer ones are missing however, so that's just true for now.
  8. To support both UP and our current websocket notifications, if the user has enabled UP, the instance account will disconnect from the websocket and vice-versa
  9. oh and, we never really requested the "push" permission from fedi servers so accounts will have to be re-authed to use UP

I think that's all, I don't know if I'm missing something. I'm slightly confused about https://unifiedpush.org/developers/ux/#unregistration. Am I supposed to re-register accounts that didn't manually unregister? 🤷

Other than that, I did run across some segfaults, but I didn't manage to dump them and can't reliably reproduce them so I don't know if they are from libunifiedpush or our recent huge refactors, so I'll have to further investigate/stress test it.

cc: @s1m does this sound okay?

image

fix: #42 --- Uses https://codeberg.org/UnifiedPush/unifiedpush-rs/ I'm not entirely sure about the UX around it, but functionality-wise it works. One deliberate decision was to skip most of the app startup when activated through dbus. That's because it's quite expensive for stuff that's left unused for handling the notification (e.g. reading secrets, network monitor, proxy, settings etc.). So it just does the bare minimum and if activated normally it will go through the proper startup code. I do think I went a bit too far with the compile time flags and might be annoying to maintain now, might have to let it call the separate function in the non-unifiedpush compilation too... --- Onto API: 1. on startup we create the unifiedpush instance and try to register a distributor. There's only 1 on Linux as far as I know, so I didn't bother with a UI yet, if there's more than 1, it will use the first it find, if there's 0, it will set a variable so we know that UP is not available. 2. if `--unifiedpush-bg` push is passed, the process will launch as a service and will die after a while (aka after it handles the message in this case). The above startup workflow will run which is basically skipping the normal workflow. 3. on new endpoint, it unregisters and then registers a webpush instance with mastodon, uses the user's settings for what types to receive and sets `unifiedpush_enabled` to true for the user if successful 4. on unregister, it unregisters from the instance and sets `unifiedpush_enabled` to false 5. on new message, it gets the account uuid from the instance (it's uuid:fedi_instance) and parses the message which includes {notification_type, icon, title, body, notification_id} among others. If it's running in `--unifiedpush-bg`, we basically just use the info from the message and send the notification as is, but if it's running in normal mode, we fetch the actual notification object from the ID and then send the notification like we'd normally do with our own titles, bodies, buttons, grouping, etc. That, again, allows this to be very optimized when the app is closed while fully featured when it's open. 6. For a user to register, they go to settings>notifications, if UnifiedPush + distributor is available there will be a clickable switch, otherwise it won't be clickable and the reason will be shown. Oh, and on registration, we also use the VAPID from the mastodon instance, if it has any. 7. When the settings window is closed, we also send the muted-notification-types to the webpush api so mastodon only sends us those we want. Some newer ones are missing however, so that's just `true` for now. 8. To support both UP and our current websocket notifications, if the user has enabled UP, the instance account will disconnect from the websocket and vice-versa 9. oh and, we never really requested the "push" permission from fedi servers so accounts will have to be re-authed to use UP I think that's all, I don't know if I'm missing something. I'm slightly confused about https://unifiedpush.org/developers/ux/#unregistration. Am I supposed to re-register accounts that didn't manually unregister? 🤷 Other than that, I did run across some segfaults, but I didn't manage to dump them and can't reliably reproduce them so I don't know if they are from libunifiedpush or our recent huge refactors, so I'll have to further investigate/stress test it. cc: @s1m does this sound okay? ![image](/attachments/b0ce3c61-2295-4023-8a98-cf2446a76fb1)
feat: unifiedpush
Some checks reported warnings
CI / lint Successful in 4s
CI / flatpak-builder / aarch64 Successful in 2m54s
CI / flatpak-builder / x86_64 Successful in 4m20s
Windows / msys2 / x86_64 Successful in 6m12s
ci/woodpecker/pr/woodpecker Pipeline was successful
Windows / msys2 / aarch64 Has been cancelled
7a79b777cf
Author
Owner
Copy link

TODO: CI flatpak libunifiedpush

TODO: CI flatpak libunifiedpush
s1m approved these changes 2026年07月13日 17:33:45 +02:00
s1m left a comment
Copy link

Overall, LGTM

There is one thing I am not sure, that may be good to look at or to implement:

  • When the app is started from the D-Bus activated service, the app runs in the background without UI.
  • It would be great if the app could close itself:
    • After a few seconds if it doesn't receive new notifications
    • If another instance of the app is started with the UI. For this second point, your opinion is important. Do you think we need to add something to the lib ?

Edit: I clicked "Approved" instead of "Comment", it isn't very relevant

Overall, LGTM There is one thing I am not sure, that may be good to look at or to implement: - When the app is started from the D-Bus activated service, the app runs in the background without UI. - It would be great if the app could close itself: - After a few seconds if it doesn't receive new notifications - If another instance of the app is started with the UI. For this second point, your opinion is important. Do you think we need to add something to the lib ? Edit: I clicked "Approved" instead of "Comment", it isn't very relevant
@ -277,0 +447,4 @@
}else{
Tuba.up_distributor_found=false;
}
}
First-time contributor
Copy link

If you want to be future-proof, you can implement a selection dialog. I wouldn't be surprised if some other distribs are published soon. But that's not critical IMO, as someone can change UNIFIEDPUSH_DISTRIBUTOR env var anyway.

If you want to be future-proof, you can implement a selection dialog. I wouldn't be surprised if some other distribs are published soon. But that's not critical IMO, as someone can change `UNIFIEDPUSH_DISTRIBUTOR` env var anyway.
Author
Owner
Copy link

I want to do that, though my only concern right now is that it might be a bit too much, for first time users to get a few dialogs up in their face the first time they run the app. I guess a better UX, would be to only show the dialog when UnifiedPush gets enabled by a user for the first time and then show it again if it can't find the one they last selected in the future

I want to do that, though my only concern right now is that it might be a bit too much, for first time users to get a few dialogs up in their face the first time they run the app. I guess a better UX, would be to only show the dialog when UnifiedPush gets enabled by a user for the first time and then show it again if it can't find the one they last selected in the future
First-time contributor
Copy link

Ok, I thought it was about the selection when the user enables the setting.

Usually:

  • The dialog is shown only if there isn't any default distrib, and if there are more than one distrib available on the system. So, most of the time it isn't shown (as most users have only one distrib)
  • When there is a default, and multiple distribs available, we offer a setting to the user to select a non-default distrib

I don't know if you've seen this page, it may help: https://unifiedpush.org/developers/ux/

Ok, I thought it was about the selection when the user enables the setting. Usually: - The dialog is shown only if there isn't any default distrib, and if there are more than one distrib available on the system. So, most of the time it isn't shown (as most users have only one distrib) - When there is a default, and multiple distribs available, we offer a setting to the user to select a non-default distrib I don't know if you've seen this page, it may help: https://unifiedpush.org/developers/ux/
First-time contributor
Copy link

Anyway, I don't think it is very important yet

Anyway, I don't think it is very important yet
Author
Owner
Copy link

@s1m wrote in #1690 (comment):

Overall, LGTM

There is one thing I am not sure, that may be good to look at or to implement:

* When the app is started from the D-Bus activated service, the app runs in the background without UI.
* It would be great if the app could close itself:
 
 * After a few seconds if it doesn't receive new notifications
 * If another instance of the app is started with the UI. For this second point, your opinion is important. Do you think we need to add something to the lib ?

GLib.Application has a flag called IS_SERVICE, which I enable when --unifiedpush-bg is passed. That spawns the app for a few seconds, goes through the startup process and then closes if the user doesn't activate/launch it normally. I tested all cases here,

  1. when a message is received and tuba is launched with --unifiedpush-bg, it skips the expensive parts of the startup (setting up the UI, networking, accounts etc) and goes straight into handling the message then it dies
  2. if the user launches the app while that's happening, the app will go through the normal startup process and launch a window

(ignore the ram usage, it's wrong, I think)

kUnifiedPush launched tuba with --unifiedpush-bg, it handled the notification and died.

For this second point, your opinion is important. Do you think we need to add something to the lib ?

So no, I don't think that's important, however, it might be worth documenting somewhere that users should pass IS_SERVICE to their GApplication. I mean, it already does that when passing --gapplication-service by default (this was the service Tuba and other GTK apps install). It's briefly mentioned on https://docs.gtk.org/gio/method.Application.run.html

Now, I don't know if it would make it easier if --gapplication-service was passed by UP instead of --unifiedpush-bg. It would be "automatic" but at the same time, personally, I prefer knowing it's UP that launched it so I can skip the expensive parts of startup.

@s1m wrote in https://codeberg.org/GeopJr/Tuba/pulls/1690#issuecomment-19136429: > Overall, LGTM > > There is one thing I am not sure, that may be good to look at or to implement: > > * When the app is started from the D-Bus activated service, the app runs in the background without UI. > > * It would be great if the app could close itself: > > * After a few seconds if it doesn't receive new notifications > * If another instance of the app is started with the UI. For this second point, your opinion is important. Do you think we need to add something to the lib ? GLib.Application has a flag called `IS_SERVICE`, which I enable when --unifiedpush-bg is passed. That spawns the app for a few seconds, goes through the startup process and then closes if the user doesn't activate/launch it normally. I tested all cases here, 1. when a message is received and tuba is launched with `--unifiedpush-bg`, it skips the expensive parts of the startup (setting up the UI, networking, accounts etc) and goes straight into handling the message then it dies 2. if the user launches the app while that's happening, the app will go through the normal startup process and launch a window <video src="/attachments/57bf0204-1997-4980-bcd1-4fcb013abb7e" title="Screencast From 2026年07月13日 18-46-15" controls></video> *(ignore the ram usage, it's wrong, I think)* kUnifiedPush launched tuba with `--unifiedpush-bg`, it handled the notification and died. > For this second point, your opinion is important. Do you think we need to add something to the lib ? So no, I don't think that's important, however, it might be worth documenting somewhere that users should pass IS_SERVICE to their GApplication. I mean, it already does that when passing `--gapplication-service` by default (this was the service Tuba and other GTK apps install). It's briefly mentioned on https://docs.gtk.org/gio/method.Application.run.html Now, I don't know if it would make it easier if `--gapplication-service` was passed by UP instead of `--unifiedpush-bg`. It would be "automatic" but at the same time, personally, I prefer knowing it's UP that launched it so I can skip the expensive parts of startup.
First-time contributor
Copy link

Thanks, so the background launch works great.

Now, I don't know if it would make it easier if --gapplication-service was passed by UP instead of --unifiedpush-bg.

You can do that if you prefer:

data/dev.geopjr.Tuba.UnifiedPush.service.in

- Exec=@BINDIR@/dev.geopjr.Tuba --unifiedpush-bg
+ Exec=@BINDIR@/dev.geopjr.Tuba --gapplication-service

So no, I don't think that's important, however, it might be worth documenting somewhere that users should pass IS_SERVICE to their GApplication. I mean, it already does that when passing --gapplication-service by default (this was the service Tuba and other GTK apps install). It's briefly mentioned on https://docs.gtk.org/gio/method.Application.run.html

Definitely agree. Where would be the best place in your opinion? As you have just implemented it, where did you look for this information? Maybe we can add an example for the D-BUS activated service in libunifiedpush readme ?

Thanks, so the background launch works great. > Now, I don't know if it would make it easier if --gapplication-service was passed by UP instead of --unifiedpush-bg. You can do that if you prefer: *data/dev.geopjr.Tuba.UnifiedPush.service.in* ```diff - Exec=@BINDIR@/dev.geopjr.Tuba --unifiedpush-bg + Exec=@BINDIR@/dev.geopjr.Tuba --gapplication-service ``` > So no, I don't think that's important, however, it might be worth documenting somewhere that users should pass IS_SERVICE to their GApplication. I mean, it already does that when passing --gapplication-service by default (this was the service Tuba and other GTK apps install). It's briefly mentioned on https://docs.gtk.org/gio/method.Application.run.html Definitely agree. Where would be the best place in your opinion? As you have just implemented it, where did you look for this information? Maybe we can add an example for the D-BUS activated service in libunifiedpush readme ?
feat(flatpak): libunifiedpush
All checks were successful
CI / lint Successful in 3s
ci/woodpecker/pr/woodpecker Pipeline was successful
Windows / msys2 / x86_64 Successful in 5m34s
CI / flatpak-builder / aarch64 Successful in 4m31s
Windows / msys2 / aarch64 Successful in 7m41s
CI / flatpak-builder / x86_64 Successful in 6m6s
f5ddf1c963
Author
Owner
Copy link

As you have just implemented it, where did you look for this information? Maybe we can add an example for the D-BUS activated service in libunifiedpush readme ?

I'd say maybe something on https://codeberg.org/UnifiedPush/unifiedpush-rs/src/branch/main/unifiedpush#start-from-a-push-notification ? The problem is that the docs will become a bit too GLib-specific then :/

## GApplication
For GLib-based applications, passing `--gapplication-service` instead or setting the [G_APPLICATION_IS_SERVICE](https://docs.gtk.org/gio/flags.ApplicationFlags.html#is_service) manually, will launch the app in "service" mode and will automatically exit after 10 seconds unless activated by the user. That will allow you to handle the UnifiedPush message without launching the GUI. [Read More](https://docs.gtk.org/gio/method.Application.run.html#description)

I just pushed libunifiedpush to the flatpak manifest used by the CI, it works

> As you have just implemented it, where did you look for this information? Maybe we can add an example for the D-BUS activated service in libunifiedpush readme ? I'd say maybe something on https://codeberg.org/UnifiedPush/unifiedpush-rs/src/branch/main/unifiedpush#start-from-a-push-notification ? The problem is that the docs will become a bit too GLib-specific then :/ ``` ## GApplication For GLib-based applications, passing `--gapplication-service` instead or setting the [G_APPLICATION_IS_SERVICE](https://docs.gtk.org/gio/flags.ApplicationFlags.html#is_service) manually, will launch the app in "service" mode and will automatically exit after 10 seconds unless activated by the user. That will allow you to handle the UnifiedPush message without launching the GUI. [Read More](https://docs.gtk.org/gio/method.Application.run.html#description) ``` --- I just pushed libunifiedpush to the flatpak manifest used by the CI, it works
Author
Owner
Copy link

So, I'll go ahead and merge, thanks for the review, help and libunifiedpush! 🙇

So, I'll go ahead and merge, thanks for the review, help and libunifiedpush! 🙇
GeopJr deleted branch feat/unifiedpush 2026年07月14日 06:37:22 +02:00
GeopJr referenced this pull request from a commit 2026年07月14日 06:37:23 +02:00
First-time contributor
Copy link

Thanks a lot!

I've updated flatpak doc: UnifiedPush/documentation@e7d88607bb

And unifiedpush-rs README for Gapplications: UnifiedPush/unifiedpush-rs@ca0e5f9120

Thanks a lot! I've updated flatpak doc: https://codeberg.org/UnifiedPush/documentation/commit/e7d88607bb371e44c905d5f67220e42fbec7e2be And unifiedpush-rs README for Gapplications: https://codeberg.org/UnifiedPush/unifiedpush-rs/commit/ca0e5f9120328e508b1dff222485cfb83d7a860f
First-time contributor
Copy link

If you're OK, I'm going to post something with UnifiedPush's Mastodon account to introduce UnifiedPush support in Tuba

If you're OK, I'm going to post something with UnifiedPush's Mastodon account to introduce UnifiedPush support in Tuba
Author
Owner
Copy link

Sure, though users won't see it until the next major release, so just don't make it sound like they can use it right away!

Sure, though users won't see it until the next major release, so just don't make it sound like they can use it right away!
Sign in to join this conversation.
No reviewers
s1m
Labels
Clear labels
akkoma
Akkoma related issue
api
Fedi software API related issue
blocked
bug
Something isn't working
calckey
Calckey related issue
composer
Composer related issue
designer input needed
documentation
Improvements or additions to documentation
duplicate
This issue or pull request already exists
enhancement
New feature or request
focus
Focus related issue
glitch-soc
glitch-soc related issue
good first issue
Good for newcomers
gotosocial
GoToSocial related issue
help wanted
Extra attention is needed
info needed
invalid
This doesn't seem right
mastodon
Mastodon related issue
mediaviewer
Mediaviewer related issue
meta
misskey
Misskey related issue (including forks)
navigation
Navigation related issue
network
Network related issue
notifications
performance
Performance related issue
pleroma
Pleroma related issue
priority: critical
priority: high
priority: low
priority: medium
question
Further information is requested
rfc
A request for comments
snap
status
Status widget related issue
takahē
Takahē related issue
windows
wontfix
This will not be worked on
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
GeopJr/Tuba!1690
Reference in a new issue
GeopJr/Tuba
No description provided.
Delete branch "feat/unifiedpush"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?