Compare commits

..

96 commits

Author SHA1 Message Date
f3dc071bd6 chore: fige Cargo.lock tel que resolu apres le merge 1.7.0
Some checks failed
Continuous Integration / formatting (push) Has been cancelled
Continuous Integration / linting (push) Has been cancelled
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 07:58:50 +02:00
7d7e763c35 Merge tag 'epoch-1.7.0' into yoda-dock-magnification
Some checks failed
Continuous Integration / formatting (push) Has been cancelled
Continuous Integration / linting (push) Has been cancelled
Epoch 1.7.0
2026-08-27 07:49:24 +02:00
Michael Murphy
40248fb220
i18n: translation update from Hosted Weblate (#1532)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for [Pop OS/COSMIC Audio
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-audio/).


It also includes following components:

* [Pop OS/COSMIC Tiling
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-tiling/)

* [Pop OS/COSMIC Bluetooth
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/)

* [Pop OS/COSMIC Accessibility
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-a11y/)

* [Pop OS/COSMIC Network
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/)

* [Pop OS/COSMIC Notifications
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-notifications/)

* [Pop OS/COSMIC Applets Desktop
Entries](https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/)

* [Pop OS/COSMIC Input Sources
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-input-sources-applet/)

* [Pop OS/COSMIC Battery
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-battery/)

* [Pop OS/COSMIC Power
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-power/)

* [Pop OS/COSMIC Workspaces
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-workspaces-applet/)

* [Pop OS/COSMIC App List
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-app-list/)

* [Pop OS/COSMIC Time
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-time/)



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widget/pop-os/cosmic-applet-audio/matrix-auto.svg)
2026-08-24 20:50:46 +02:00
Hosted Weblate
755468b385
i18n: translation updates from weblate
Co-authored-by: CodeBeats <nicholai67@hotmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jens Peter Nielsen <mail@jp-nielsen.dk>
Co-authored-by: Michael Millet <mikevlet@protonmail.com>
Co-authored-by: lorduskordus <lorduskordus@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-a11y/cs/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/da/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/da/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-notifications/da/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-power/da/
Translation: Pop OS/COSMIC Accessibility Applet
Translation: Pop OS/COSMIC Bluetooth Applet
Translation: Pop OS/COSMIC Network Applet
Translation: Pop OS/COSMIC Notifications Applet
Translation: Pop OS/COSMIC Power Applet
2026-08-22 19:36:08 +02:00
b2126a9f2b fix: appeler refresh_volume_limits sur app, pas self
Some checks failed
Continuous Integration / formatting (push) Has been cancelled
Continuous Integration / linting (push) Has been cancelled
Le patch yoda 5b838a67 est conserve (il initialise les plafonds sink ET
source, contrairement au code inline amont) mais il est desormais dans
une closure recevant `app: &mut Self` : l appel sur `self` ne compilait
plus.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 22:18:37 +02:00
673547dc52 Merge remote-tracking branch 'upstream/master' into yoda-dock-magnification
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	cosmic-app-list/src/app.rs
#	cosmic-applet-audio/src/lib.rs
#	cosmic-applet-input-sources/Cargo.toml
#	cosmic-applet-input-sources/src/lib.rs
#	cosmic-applet-status-area/src/components/app.rs
#	cosmic-panel-button/src/lib.rs
2026-08-19 22:04:42 +02:00
Michael Aaron Murphy
73d727814c fix(audio): debounce volume updates by 128ms 2026-08-18 17:23:33 +02:00
Michael Murphy
a1d4be5d10
i18n: translation update from Hosted Weblate (#1511) 2026-08-18 14:06:42 +02:00
Hosted Weblate
4285090a44
i18n: translation updates from weblate
Co-authored-by: Andi Chandler <andi@gowling.com>
Co-authored-by: CodeBeats <nicholai67@hotmail.com>
Co-authored-by: David Carvalho <david.snt.carvalho@gmail.com>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Feike Donia <feikedonia@proton.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hugo Carvalho <hugokarvalho@hotmail.com>
Co-authored-by: Jens Peter Nielsen <mail@jp-nielsen.dk>
Co-authored-by: Martin Meizoso <martinmeizoso@gmail.com>
Co-authored-by: Michael Millet <mikevlet@protonmail.com>
Co-authored-by: Prasanta-Hembram <Prasantahembram720@gmail.com>
Co-authored-by: Quentin PAGÈS <quentinantonin@free.fr>
Co-authored-by: Steen Rabol <steen@rabol.dev>
Co-authored-by: Walter William Beckerleg Bruckman <spayk.99@protonmail.com>
Co-authored-by: Wilbur Lindqvist <william@willmantrade.com>
Co-authored-by: lucamosca1 <luca.moscato@gmail.com>
Co-authored-by: sicKat <giuseppecatillo2003@gmail.com>
Co-authored-by: the-forest-watches <lucastronks@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/da/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/en_GB/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/gl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/oc/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/sat/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-a11y/da/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-a11y/gl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-audio/da/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-audio/en_GB/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-audio/gl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-battery/da/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-battery/en_GB/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-battery/gl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-battery/it/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-battery/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-battery/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/da/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/en_GB/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/gl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/da/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/en_GB/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/gl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-notifications/gl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-power/gl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-tiling/gl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-tiling/nl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-time/gl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/da/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/gl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/it/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/nl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/oc/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/pt/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/sat/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-input-sources-applet/da/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-input-sources-applet/gl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-workspaces-applet/gl/
Translation: Pop OS/COSMIC Accessibility Applet
Translation: Pop OS/COSMIC App List Applet
Translation: Pop OS/COSMIC Applets Desktop Entries
Translation: Pop OS/COSMIC Audio Applet
Translation: Pop OS/COSMIC Battery Applet
Translation: Pop OS/COSMIC Bluetooth Applet
Translation: Pop OS/COSMIC Input Sources Applet
Translation: Pop OS/COSMIC Network Applet
Translation: Pop OS/COSMIC Notifications Applet
Translation: Pop OS/COSMIC Power Applet
Translation: Pop OS/COSMIC Tiling Applet
Translation: Pop OS/COSMIC Time Applet
Translation: Pop OS/COSMIC Workspaces Applet
2026-08-18 07:51:49 +02:00
Rafael Kraut
63292ab74d fix(status-area): constrain menu icon-data to icon size
Menu items expose their icon as a themed `icon-name` or raw PNG `icon-data`. The named-icon branch renders at a fixed `.size(14)`, but the icon-data branch wrapped the pixmap in a bare `Image` with no size constraint, so it rendered at the PNG's native dimensions. Apps may send full-resolution pixmaps, which then render oversized in the menu.

Constrain icon-data to the same 14px box with ContentFit::Contain, matching how KDE (Qt small-icon size) and GNOME (St.Icon popup-menu-icon) render menu item icons.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-10 21:28:12 +02:00
fb33b39cec fix(network): align applet with cosmic-settings 1.3 nmrs backend
Some checks failed
Continuous Integration / formatting (push) Has been cancelled
Continuous Integration / linting (push) Has been cancelled
cosmic-settings 1.3 replaced its custom NetworkManager backend with nmrs
and removed subscriptions/network-manager, which cosmic-applet-network
still referenced by path. That broke manifest resolution for the whole
workspace, so no applet package could be built at all.

Take upstream's post-migration cosmic-applet-network wholesale (upstream
e318f0d4 "migrate NetworkManager flow to nmrs 3.4" plus the nine network
fixes that followed it). None of our local commits touched this crate's
sources, only its manifest, so the only local adjustments to reapply are
the libcosmic -> cosmic workspace alias and the 1.3.0 version.

This also drops the cosmic-dbus-networkmanager dependency, and with it
the last user of the now-dead cosmic-settings-network-manager and
cosmic-settings-sound patch entries, which are removed from the
workspace manifest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 09:47:24 +02:00
5b838a6795 fix(audio): initialize volume ceilings at startup
max_sink_volume and max_source_volume were only computed in the
TogglePopup handler, so they kept their Default value of 0 until the
popup had been opened once. The scroll-to-adjust handler clamps against
max_sink_volume, which meant every wheel event resolved to
SetSinkVolume(0) until the user had clicked the applet icon: scrolling
up did nothing and scrolling down muted the sink.

Extract refresh_volume_limits() and call it from init() as well as from
TogglePopup, so the popup still picks up amplification config changes
made during the session.

This is the actual cause of "scroll does not adjust volume until the
applet is clicked once" — the wheel events were reaching the widget all
along, which is why a single click made scrolling work on plain hover
from then on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 09:33:16 +02:00
a9f15d67a2 fix: handle wheel events before cursor bounds check in MouseArea
Some checks failed
Continuous Integration / formatting (push) Has been cancelled
Continuous Integration / linting (push) Has been cancelled
Move the on_mouse_wheel handler before the cursor.is_over(bounds)
guard. The panel's nested compositor only routes axis events to the
applet surface when the pointer is over it, making the bounds check
redundant for scroll events. This fixes scroll-to-adjust-volume not
working on the audio applet after a fresh login, when the cursor
coordinates sent by the panel may not match the applet's surface-local
coordinate space.
2026-07-29 11:59:32 +02:00
Michael Murphy
ec8ffdc85d
i18n: translation update from Hosted Weblate (#1446)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for [Pop OS/COSMIC Audio
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-audio/).


It also includes following components:

* [Pop OS/COSMIC Accessibility
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-a11y/)

* [Pop OS/COSMIC Power
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-power/)

* [Pop OS/COSMIC Time
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-time/)

* [Pop OS/COSMIC Network
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/)

* [Pop OS/COSMIC Input Sources
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-input-sources-applet/)

* [Pop OS/COSMIC Applets Desktop
Entries](https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/)

* [Pop OS/COSMIC App List
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-app-list/)

* [Pop OS/COSMIC Battery
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-battery/)

* [Pop OS/COSMIC Bluetooth
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/)

* [Pop OS/COSMIC Workspaces
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-workspaces-applet/)

* [Pop OS/COSMIC Tiling
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-tiling/)

* [Pop OS/COSMIC Notifications
Applet](https://hosted.weblate.org/projects/pop-os/cosmic-applet-notifications/)



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widget/pop-os/cosmic-applet-audio/horizontal-auto.svg)
2026-07-28 16:44:41 +02:00
Hosted Weblate
7ef0084a24
i18n: translation updates from weblate
Co-authored-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
Co-authored-by: Ahrakiel <christopherbatrier@gmail.com>
Co-authored-by: Aman Alam <aalam@users.noreply.hosted.weblate.org>
Co-authored-by: Aman Shaw <shaw4m4n@netc.fr>
Co-authored-by: Andi Chandler <andi@gowling.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: AriyA66Y <ariya66y@protonmail.com>
Co-authored-by: CYAXXX <85353920+CYAXXX@users.noreply.github.com>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Fedorov Alexei <aleksejfedorov963@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Isaac Subirana <isaacsubiranac@gmail.com>
Co-authored-by: Jim Spentzos <jimspentzos2000@gmail.com>
Co-authored-by: Kartik Nayak <65574961+kodebarista@users.noreply.github.com>
Co-authored-by: Nara Díaz Viñolas <nara.diaz.vinolas@gmail.com>
Co-authored-by: Prasanta-Hembram <Prasantahembram720@gmail.com>
Co-authored-by: Saeed <s.zare.org.82@gmail.com>
Co-authored-by: Sproston <ojspr2007@gmail.com>
Co-authored-by: Tadas Misiūnas <tadujo@gmail.com>
Co-authored-by: VandaL <vandalhj@gmail.com>
Co-authored-by: Vilius Paliokas <viliuspaliokas@gmail.com>
Co-authored-by: Wilbur Lindqvist <william@willmantrade.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: lorduskordus <lorduskordus@gmail.com>
Co-authored-by: sicKat <giuseppecatillo2003@gmail.com>
Co-authored-by: taijuin Lee <taijuin@gmail.com>
Co-authored-by: therealmate <hellogaming91@gmail.com>
Co-authored-by: Димко <Dymkovych@proton.me>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/ar/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/cs/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/en_GB/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/fa/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/hi/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/it/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/lt/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/pa/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/pl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/ru/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/uk/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-a11y/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-a11y/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-a11y/en_GB/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-a11y/fa/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-a11y/hi/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-a11y/it/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-a11y/sat/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-audio/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-audio/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-audio/it/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-audio/lt/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-audio/sat/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-battery/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-battery/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/en_GB/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/fa/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/hi/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/it/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/sat/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/en_GB/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/fr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/sat/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-notifications/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-power/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-power/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-tiling/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-tiling/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-time/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-time/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/cs/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/en_GB/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/fa/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/hi/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/it/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-input-sources-applet/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-input-sources-applet/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-workspaces-applet/ca/
Translation: Pop OS/COSMIC Accessibility Applet
Translation: Pop OS/COSMIC App List Applet
Translation: Pop OS/COSMIC Applets Desktop Entries
Translation: Pop OS/COSMIC Audio Applet
Translation: Pop OS/COSMIC Battery Applet
Translation: Pop OS/COSMIC Bluetooth Applet
Translation: Pop OS/COSMIC Input Sources Applet
Translation: Pop OS/COSMIC Network Applet
Translation: Pop OS/COSMIC Notifications Applet
Translation: Pop OS/COSMIC Power Applet
Translation: Pop OS/COSMIC Tiling Applet
Translation: Pop OS/COSMIC Time Applet
Translation: Pop OS/COSMIC Workspaces Applet
2026-07-28 08:03:20 +00:00
2d78a0abff chore: bump version to 1.3.0
Some checks failed
Continuous Integration / linting (push) Has been cancelled
Continuous Integration / formatting (push) Has been cancelled
2026-07-22 15:57:00 +02:00
Akrm Al-Hakimi
48c3633151 fix(network): re-register secret agent after NetworkManager restart 2026-07-17 16:59:55 +02:00
Hojjat
e544cb54c2 chore: update libcosmic to fix minimize applet crash 2026-07-13 17:17:24 -06:00
Ian Douglas Scott
d86c5631ed input-sources: Do not store full CosmicCompConfig
`update_xkb()` was previously never called before the subscription first
returns a config value, so not getting an initial value of `comp_config`
at start before the subscription does not change behavior.
2026-07-13 13:18:56 -07:00
Ian Douglas Scott
9acfd8d8c1 input-sources: Use cosmic-keyboard-layout-unstable-v1
https://github.com/pop-os/cosmic-protocols/pull/28

Continues to use `xkg_config` to get list of layouts, but the group is
now changed by modifying the group while leaving the config unchanged.
And changes to the group are now reflected in the applet.
2026-07-13 13:18:56 -07:00
Akrm Al-Hakimi
ba490e76af fix: normalize connections that match saved VPN profiles 2026-07-10 16:59:47 -06:00
Akrm Al-Hakimi
67415262d5 fix: show pending VPN state in applet
Track VPN activation and deactivation locally while nmrs operations are
running. This lets the applet show a spinner immediately, refresh state
while NetworkManager settles, and clear pending state on completion or
error.

Also use VPN-specific icons for VPN rows and prefer active or pending VPN
state for the panel icon.
2026-07-10 16:59:47 -06:00
Akrm Al-Hakimi
9025430cf2 fix: keep inactive known Wi-Fi rows visible
Only mark an nmrs Wi-Fi group as activated when the group itself is
active. Access points inherit device state, so inactive known networks on
a connected Wi-Fi device can otherwise appear activated and get skipped
from the known networks section while also being filtered out of visible
networks.
2026-07-10 16:59:47 -06:00
Akrm Al-Hakimi
829d66c44a fix: filter ssids by raw bytes to remove hidden network entries 2026-07-10 16:59:47 -06:00
akrm al-hakimi
74e2908987 chore: remove dead arm in DeviceState match 2026-07-10 16:59:47 -06:00
akrm al-hakimi
6fd8309cf7 chore: remove dead utils file 2026-07-10 16:59:47 -06:00
akrm al-hakimi
f69e943d91 fix(network): restore IPv4 display and wifi list ordering
- Strip the CIDR suffix (/24) from the shown IPv4 address
- Sort visible wireless networks by signal strength again
- Filter out empty-SSID "hidden network" entries from the list
2026-07-10 16:59:47 -06:00
Akrm Al-Hakimi
a3be51560f fix: dupe connection names; incorrect ipv6 labeling
Fixes a bug where we showed `Activated` networks after the current active
one.

Also ipv4/6 addresses were merged so this seperates them and only shows ipv4
for now until we decide the best way to show both, UI/UX wise.
2026-07-10 16:59:47 -06:00
Akrm Al-Hakimi
e318f0d412 cosmic-applet-network: migrate NetworkManager flow to nmrs 3.4 2026-07-10 16:59:47 -06:00
Hojjat
b282484463 chore: update libcosmic to fix minimize applet crash 2026-07-10 00:18:30 -06:00
Ashley Wulber
9621be48b3 chore: update libcosmic 2026-07-09 17:48:08 -06:00
Ashley Wulber
3f4d0f43a0
fix: update iced (#1468)
popup blur & corner cleanup
2026-07-09 09:42:22 -06:00
Jeremy Soller
0543dd7441
Theme v2 (#1455) 2026-07-06 07:12:26 -06:00
ebecfa6b7d chore: refresh lockfile post-upstream rebase
Some checks failed
Continuous Integration / formatting (push) Has been cancelled
Continuous Integration / linting (push) Has been cancelled
Leyoda 2026 – GPLv3
2026-07-04 11:20:22 +02:00
adc97dc8f1 fix: adapt to cosmic-theme background(transparent) accessor
Leyoda 2026 – GPLv3
2026-07-04 11:19:28 +02:00
ced6eb267e Fix status menu build after upstream rebase 2026-07-04 11:19:28 +02:00
04acc50e4d feat: add themed launcher icon catalog 2026-07-04 11:19:28 +02:00
8d56d0b500 fix: open launcher editor in existing popup 2026-07-04 11:19:28 +02:00
d58fe9d74c feat: add editable dock launchers 2026-07-04 11:19:28 +02:00
9677159db6 chore: fix applets redeploy path 2026-07-04 11:19:28 +02:00
3c360142ae chore: use local COSMIC support crates 2026-07-04 11:19:28 +02:00
ebfaa4eb7b chore: use local cosmic-text checkout 2026-07-04 11:19:28 +02:00
0023a462ba yoda: use local dbus settings bindings 2026-07-04 11:19:28 +02:00
b4fbfe168a chore: align applets with local stack 2026-07-04 11:02:35 +02:00
Votre Nom
c117f172cb fix(audio): accumuler les rafales scroll Pixels au lieu de signum()
Avec Wayland axis_v120 (scroll haute-résolution sur souris HID modernes),
un cran physique génère 5–8 events ScrollDelta::Pixels (~15–20px chacun).
L'ancien code passait chaque sub-event par .signum() puis -1/+1 à sink_volume,
donc un seul cran physique faisait varier le volume de 5 à 40% — résultat :
scroll up sur l'icône audio panel / dock coupait le son si le volume était
déjà bas.

Fix : thread_local accumulator des deltas Pixels, émission seulement
au passage du seuil de 15px par cran logique. Lines (souris classique
sans axis_v120) reste proportionnel y * WHEEL_STEP. round() au lieu de
truncation finale pour ne pas perdre les fractions de pourcent.

Leyoda 2026 - GPLv3
2026-07-04 10:55:18 +02:00
Votre Nom
66392fc42a chore: add redeploy.sh for /usr/local/bin install
Builds workspace release, backs up existing binaries, installs
cosmic-applets/cosmic-app-list/cosmic-panel-button to /usr/local/bin
(precedence over pacman package via $PATH).

Leyoda 2026 – GPLv3
2026-07-04 10:55:18 +02:00
Votre Nom
c15a879c31 fix(wayland): graceful exit on compositor disconnect
Replace 3 panicking unwrap() in cosmic-app-list/wayland_handler.rs
(event loop dispatch + 2 conn.flush in screencopy) with logged
errors that break/return None instead.

Wrap cosmic-applets/main.rs entry point in panic::catch_unwind to
catch panics propagating from libcosmic/iced/winit (which we cannot
patch locally without forking) when the COSMIC compositor closes
the Wayland connection at logout. This eliminates the cascade of
~12 SIGABRT coredumps observed at session shutdown.

Panic strategy is unwind (default), catch_unwind is sound here.

Leyoda 2026 – GPLv3
2026-07-04 10:55:18 +02:00
d5c2a19e95 yoda: smooth animated fisheye for dock hover (phase B v3, closes b)
Inter-icon hover changes were snapping because icon_scale_for read the
hovered icon's real rectangle directly. This adds a small animation
layer so the bell center lerps toward the target and the whole effect
fades in/out at the dock's edges.

CosmicAppList gains three fields:
- anim_hover_center: Option<(f32, f32)> — virtual cursor position,
  chases the hovered icon's center across ticks.
- anim_hover_intensity: f32 (0..1) — global fade-in/out of the
  fisheye. Target 1.0 while a dock icon is hovered, 0.0 otherwise.
- anim_last_tick: Option<Instant> — for dt-based exponential smoothing
  (time-constant tau = 60ms, ~99% of target reached in ~120ms).

A new Message::AnimTick(Instant) is emitted at ~60 fps by a
conditional iced::time::every subscription — only active when the
pointer is over a dock icon OR intensity hasn't faded back to ~0 yet,
so the panel stays idle when no one is hovering the dock.

icon_scale_for now reads anim_hover_center instead of rectangles[hovered]
and multiplies the bell's peak by anim_hover_intensity. Behaviour:
- Pointer slides A → B: bell glides continuously, both icons animate.
- Pointer enters dock: icons inflate smoothly over ~120 ms.
- Pointer leaves dock: icons deflate smoothly over ~120 ms.

Fallback paths (first frame, missing rectangles) still respond
instantly so the feature never looks 'stuck' before the animation
kicks in.
2026-07-04 10:55:18 +02:00
6f88c6fea4 yoda: fisheye magnification for dock hover (phase B v2 / c)
Replaces the binary 1.3× hover with a true gaussian bell curve — the
hovered icon still peaks at ~1.35×, but the ±1 neighbours also bulge
noticeably, ±2 a bit, and ±3+ relax to 1.0×. Footprint ~5 icons wide,
matching the macOS Dock fisheye feel.

Implementation in fn icon_scale_for(id):
- Reads the hovered icon's and the current icon's bounds from
  self.rectangles (already populated by the existing RectangleTracker
  subscription — no new plumbing).
- Distance = |this_center - hovered_center| along the panel's long axis
  (horizontal for Top/Bottom anchors, vertical for Left/Right).
- sigma = hovered_extent * 1.4 so the bell's half-width matches one
  icon width (neighbors clearly pulled, far icons untouched).
- scale = 1.0 + PEAK * exp(-(d/sigma)²) with PEAK = 0.35.
- Falls back to binary 1.35×/1.0× when rectangle data isn't populated
  yet (first render / resize) — visibly responsive even before the
  tracker catches up.

No widget signature changes vs v1, just a smarter formula. All five
as_icon call sites already pass the result of icon_scale_for so this
update propagates everywhere.

Still on the TODO list: smooth animation (b). Right now icon→icon
transitions snap instantly; a smoothed_hover_center + tick subscription
would lerp it. Deferred to a follow-up commit.
2026-07-04 10:55:18 +02:00
1ce1d39ea3 yoda: dock icon hover magnification (macOS Tahoe-style, phase B v1)
First pass at the signature macOS Dock effect — the icon under the
pointer grows, adjacent icons stay at base size. Full fisheye (smooth
bell-curve scaling on neighbors) can be a later iteration.

Changes in cosmic-app-list/src/app.rs:
- CosmicAppList gains a hovered_dock_item: Option<DockItemId>
  auto-initialized to None via #[derive(Default)].
- New Message::DockItemHover(Option<DockItemId>) handled in update()
  by just writing the field; view() then reads it to decide scale.
- DockItem::as_icon gains an icon_scale: f32 parameter. Inside it the
  cosmic_icon width/height = (base_icon_size * icon_scale) clamped
  to u16; indicator dot and other surrounding layout stay at base
  size so only the icon visually bulges.
- New App::icon_scale_for(id) helper: 1.3 if Some(id) == hovered,
  1.0 otherwise. Single place to tune the magnification factor.
- The two main dock rows (favorites + filtered_active_list) wrap
  their rendered applet_tooltip in widget::mouse_area with
  on_enter(DockItemHover(Some(id))) / on_exit(DockItemHover(None))
  and call icon_scale_for before rendering.
- The three remaining as_icon call sites (DnD preview, favorites
  overflow popup, active overflow popup) pass icon_scale = 1.0 —
  hover magnification on those surfaces would look jittery and isn't
  needed anyway.

Build: cargo build --release -p cosmic-app-list (≈ 7s). Binary
installed at /usr/local/bin/cosmic-app-list, backup kept as
.pre-magnification.
2026-07-04 10:55:18 +02:00
Ilia Malanin
d726dd890e fix(bluetooth): do not filter out already found devices 2026-07-02 12:46:20 -06:00
William Fish
fddf3d569b
fix(status-area): forward SNI scroll events (#1431)
StatusNotifierItems can expose a Scroll(int delta, string orientation) method for tray hosts to call when the user wheels over an item. The status-area applet handled activation and context menus, but did not forward wheel events to that method.

Track the hovered tray item only while the pointer is over an icon, ignore wheel events already captured by widgets, accumulate fine-grained wheel deltas into discrete scroll steps, and call Scroll on the item proxy. Add unit coverage for the delta/orientation mapping and pixel accumulation.

Signed-off-by: William Fish <william.michael.fish@gmail.com>
2026-07-02 18:02:40 +02:00
Ashley Wulber
69d358ec34 chore: cleanup and update deps 2026-07-01 23:26:15 -04:00
Ashley Wulber
1472f82061 chore: fix popup corners and doubled opacity 2026-06-30 13:17:39 -04:00
Ashley Wulber
7f4b1ab7be chore: update deps 2026-06-30 13:17:39 -04:00
Ashley Wulber
065551ed1b chore: update libcosmic 2026-06-30 13:17:39 -04:00
Ashley Wulber
74b6202686 fix: right click popup corners 2026-06-30 13:17:39 -04:00
Ashley Wulber
f0ca9e7aee chore: update libcosmic 2026-06-30 13:17:39 -04:00
Ashley Wulber
de1884e032 chore: update deps 2026-06-30 13:17:39 -04:00
Ashley Wulber
405fddda2d wip: blur 2026-06-30 13:17:37 -04:00
William Fish
0441c67b10
fix(battery): show plug icon without battery (#1432) 2026-06-29 16:58:04 -04:00
Fred
1f7c59fb2b
feat(bluetooth): use spinner widget from libcosmic for connecting and pairing states 2026-06-26 23:56:47 +02:00
Fred
c2a00a36a8
feat(network): use libcosmic spinner for connecting states 2026-06-26 18:30:06 +02:00
LKramer
1473e1f008
fix(network): register secret agent at NetworkManager's expected object path
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:05:36 +02:00
Michael Murphy
2c95057716
i18n: translation update from Hosted Weblate (#1418) 2026-06-23 17:11:07 +02:00
Hosted Weblate
f022b6c1a9
i18n: translation updates from weblate
Co-authored-by: CYAXXX <85353920+CYAXXX@users.noreply.github.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hugo Carvalho <hugokarvalho@hotmail.com>
Co-authored-by: Isaac Subirana <isaacsubiranac@gmail.com>
Co-authored-by: Jun Hwi Ku <siguning@gmail.com>
Co-authored-by: Walter William Beckerleg Bruckman <spayk.99@protonmail.com>
Co-authored-by: Yuya Furukuwa <junclegrow@gmail.com>
Co-authored-by: therealmate <hellogaming91@gmail.com>
Co-authored-by: Димко <Dymkovych@proton.me>
Co-authored-by: 김유빈 <k.sein1016@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-a11y/ja/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-a11y/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-audio/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-audio/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-battery/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-battery/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-notifications/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-notifications/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-power/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-power/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-power/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-tiling/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-tiling/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-time/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/ja/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/ko/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/pt/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/uk/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-input-sources-applet/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-workspaces-applet/kmr/
Translation: Pop OS/COSMIC Accessibility Applet
Translation: Pop OS/COSMIC App List Applet
Translation: Pop OS/COSMIC Applets Desktop Entries
Translation: Pop OS/COSMIC Audio Applet
Translation: Pop OS/COSMIC Battery Applet
Translation: Pop OS/COSMIC Bluetooth Applet
Translation: Pop OS/COSMIC Input Sources Applet
Translation: Pop OS/COSMIC Network Applet
Translation: Pop OS/COSMIC Notifications Applet
Translation: Pop OS/COSMIC Power Applet
Translation: Pop OS/COSMIC Tiling Applet
Translation: Pop OS/COSMIC Time Applet
Translation: Pop OS/COSMIC Workspaces Applet
2026-06-23 08:02:17 +02:00
Ilia Malanin
f97dc5a31e fix(minimize): cache captured data for minimized toplevels 2026-06-22 16:58:50 -06:00
Ashley Wulber
456d052535 improv(audio): adjust popup size based on window size 2026-06-22 22:09:48 +02:00
Michael Murphy
95eda40878
feat(sound): use cosmic-settings-daemon's varlink API 2026-06-15 21:09:25 +02:00
BrianHotopp
8981b0b48e
fix(network): skip assumed connections in VPN list
`cosmic-applet-network` listed every connection from `nm.list_saved_connections()` in the VPN dropdown, including the in-memory-only profiles NetworkManager auto-generates for externally-managed interfaces (e.g. a `wg-quick@wg0.service` tunnel). Toggling such an "assumed" connection off deletes it from NM — it was never persisted — leaving a dead toggle with no way back through the applet or `nmcli con up`.

Skip connections flagged `unsaved` (in-memory only) at the `load_vpns()` site so they no longer get a togglable entry. The active-connections section still shows the interface as connected (read-only). One-line change in `cosmic-applet-network/src/app.rs`.

## Test plan

- Built on Pop!_OS and ran the patched `cosmic-applet-network`: with `wg-quick@wg0` up and NM tracking `wg0` as connected-externally, the VPN dropdown no longer shows the destructive `wg0` toggle; the active-connections section still shows `wg0`; Wi-Fi toggling is unaffected.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 19:23:45 +02:00
Michael Aaron Murphy
5428a86370 chore: bump versions to 1.0.15 2026-05-29 17:15:35 +02:00
Michael Aaron Murphy
61bd9b0dfd chore: update dependencies with fix for battery applet panic 2026-05-29 17:15:35 +02:00
Michael Aaron Murphy
c86daef96a build: use cargo vendor --locked 2026-05-29 17:15:35 +02:00
Priyam Sarkar
a50c3d499c
fix(bluetooth): bluetooth toggle is off despite active connection 2026-05-29 17:13:51 +02:00
Ashley Wulber
a4e6ecb280 fix(network): align name with spec 2026-05-29 16:20:40 +02:00
Jeremy Soller
ee85d07c48
chore: update cosmic-freedesktop-icons to fix dropbox tray icon (#1419) 2026-05-27 13:10:00 -06:00
Hojjat
010931d6d0 chore: update cosmic-freedesktop-icons to fix dropbox tray icon 2026-05-26 13:31:21 -06:00
Jeremy Soller
377f129611
i18n: translation update from Hosted Weblate (#1403) 2026-05-26 08:33:04 -06:00
Hojjat
46a8b9182b chore: update libcosmic 2026-05-26 10:04:40 -04:00
Hojjat
d73ef7dcfb fix: search the theme path provided by the app to find status-area icons 2026-05-26 10:04:40 -04:00
Hosted Weblate
88da7797bd
i18n: translation updates from weblate
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Isaac Subirana <isaacsubiranac@gmail.com>
Co-authored-by: therealmate <hellogaming91@gmail.com>
Co-authored-by: Димко <Dymkovych@proton.me>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-audio/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-battery/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-notifications/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-power/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-power/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-tiling/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/uk/
Translation: Pop OS/COSMIC Applets Desktop Entries
Translation: Pop OS/COSMIC Audio Applet
Translation: Pop OS/COSMIC Battery Applet
Translation: Pop OS/COSMIC Bluetooth Applet
Translation: Pop OS/COSMIC Network Applet
Translation: Pop OS/COSMIC Notifications Applet
Translation: Pop OS/COSMIC Power Applet
Translation: Pop OS/COSMIC Tiling Applet
2026-05-25 11:13:15 +00:00
Akrm Al-Hakimi
03c302d138 chore(deps): bump nmrs to 3.1.3 2026-05-19 10:27:27 -04:00
Akrm Al-Hakimi
2362e7ce40 chore(deps): bump nmrs version to include patch 2026-05-19 10:27:27 -04:00
Akrm Al-Hakimi
8a39826623 chore: bump nmrs version 2026-05-19 10:27:27 -04:00
Akrm Al-Hakimi
8d84396e57 feat(network): integrate nmrs for VPN, airplane mode, and secret agent
Replace cosmic-settings-network-manager-subscription channel-based
NetworkManager calls with direct nmrs API calls:
- VPN list: nm.list_saved_connections() instead of hand-walking
  NetworkManagerSettings over D-Bus.
- VPN connect/disconnect: nm.connect_vpn_by_uuid / disconnect_vpn_by_uuid.
- Forget Wi-Fi: nm.forget(&ssid).
- Wi-Fi radio toggle: nm.set_wireless_enabled.
- Airplane mode: nm.set_airplane_mode, dropping
  cosmic-settings-airplane-mode-subscription.
- Secret agent: nmrs::agent::SecretAgent registered per-popup,
  replacing nm-secret-agent-manager. VPN secrets are replied via
  responder.vpn_secrets(...); the applets own Authenticate flow still
  drives Wi-Fi password handoff and releases NM with NoSecrets.
2026-05-19 10:27:27 -04:00
Andrei Ivanou
65a9e142b5 fix(process): prevent zombie shell processes 2026-05-18 20:34:01 +02:00
Ashley Wulber
666f0110d6 clippy 2026-05-13 12:41:17 -04:00
Ashley Wulber
78a6f78621 chore: release profile 2026-05-13 12:41:17 -04:00
Ashley Wulber
8b2ff3df73 chore(battery): update deps with fix 2026-05-13 12:41:17 -04:00
Michael Murphy
89a149034d
i18n: translation update from Hosted Weblate (#1382) 2026-05-12 17:02:41 +02:00
Hosted Weblate
737aaff4b0
i18n: translation updates from weblate
Co-authored-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
Co-authored-by: Baurzhan Muftakhidinov <baurthefirst@gmail.com>
Co-authored-by: BoneNI <bounkirdni@gmail.com>
Co-authored-by: Dan <jonweblin2205@protonmail.com>
Co-authored-by: Fedorov Alexei <aleksejfedorov963@gmail.com>
Co-authored-by: Feike Donia <feikedonia@proton.me>
Co-authored-by: Geeson Wan <wang14240@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Isaac Subirana <isaacsubiranac@gmail.com>
Co-authored-by: Jim Spentzos <jimspentzos2000@gmail.com>
Co-authored-by: Julien Brouillard <julienbrouillard1@gmail.com>
Co-authored-by: Konstantinos <kostas.lampropoulos94@gmail.com>
Co-authored-by: Nara Díaz Viñolas <nara.diaz.vinolas@gmail.com>
Co-authored-by: therealmate <hellogaming91@gmail.com>
Co-authored-by: Димко <Dymkovych@proton.me>
Co-authored-by: Марко М. Костић <marko.m.kostic@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/fr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/kk/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/ru/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/sr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-app-list/uk/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-a11y/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-a11y/sr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-audio/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-audio/sr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-battery/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-battery/sr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-battery/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-bluetooth/sr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/sr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-network/uk/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-notifications/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-notifications/sr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-power/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-power/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-power/sr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-tiling/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-tiling/sr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-time/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applet-time/sr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/sr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-applets-desktop-entries/uk/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-input-sources-applet/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-input-sources-applet/sr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-workspaces-applet/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-workspaces-applet/sr/
Translation: Pop OS/COSMIC Accessibility Applet
Translation: Pop OS/COSMIC App List Applet
Translation: Pop OS/COSMIC Applets Desktop Entries
Translation: Pop OS/COSMIC Audio Applet
Translation: Pop OS/COSMIC Battery Applet
Translation: Pop OS/COSMIC Bluetooth Applet
Translation: Pop OS/COSMIC Input Sources Applet
Translation: Pop OS/COSMIC Network Applet
Translation: Pop OS/COSMIC Notifications Applet
Translation: Pop OS/COSMIC Power Applet
Translation: Pop OS/COSMIC Tiling Applet
Translation: Pop OS/COSMIC Time Applet
Translation: Pop OS/COSMIC Workspaces Applet
2026-05-11 12:24:01 +02:00
Michael Aaron Murphy
c003924f08 chore: update all dependencies 2026-04-28 20:35:07 +02:00
Michael Aaron Murphy
0932bf4edf debian: update changelog 2026-04-28 20:35:07 +02:00
Michael Aaron Murphy
11d99c5df3 chore: add zed editor config 2026-04-28 20:35:07 +02:00
Michael Aaron Murphy
b7b768a998 chore: update libcosmic 2026-04-28 20:35:07 +02:00
Michael Aaron Murphy
ce51b784b7 chore: update MSRV to Rust 1.93 2026-04-28 20:35:07 +02:00
229 changed files with 7045 additions and 3487 deletions

View file

@ -19,11 +19,11 @@ jobs:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.90.0
toolchain: 1.93.1
components: clippy
- name: install dependencies
run: sudo apt update && sudo apt install -y libxkbcommon-dev libwayland-dev libdbus-1-dev libpulse-dev libpipewire-0.3-dev libinput-dev
- uses: actions-rs-plus/clippy-check@v2
with:
toolchain: 1.90.0
toolchain: 1.93.1
args: --all --all-targets --all-features

15
.zed/settings.json Normal file
View file

@ -0,0 +1,15 @@
{
"format_on_save": "on",
"lsp": {
"rust-analyzer": {
"initialization_options": {
"check": {
"command": "clippy",
},
"rustfmt": {
"extraArgs": ["+nightly"],
},
},
},
},
}

2362
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -25,11 +25,11 @@ resolver = "3"
[workspace.dependencies]
anyhow = "1.0.102"
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d0e95be" }
cctk = { package = "cosmic-client-toolkit", path = "../cosmic-protocols/client-toolkit" }
cosmic-applets-config = { path = "cosmic-applets-config" }
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", default-features = false, features = [
cosmic-protocols = { path = "../cosmic-protocols", default-features = false, features = [
"client",
], rev = "d0e95be" }
]}
futures = "0.3"
futures-util = "0.3"
@ -38,7 +38,7 @@ i18n-embed = { version = "0.16.0", features = [
"desktop-requester",
] }
i18n-embed-fl = "0.10"
libcosmic = { git = "https://github.com/pop-os/libcosmic", default-features = false, features = [
cosmic = { package = "libcosmic-yoda", path = "../libcosmic", default-features = false, features = [
"applet",
"applet-token",
"dbus-config",
@ -48,6 +48,7 @@ libcosmic = { git = "https://github.com/pop-os/libcosmic", default-features = fa
"desktop-systemd-scope",
"winit",
] }
cosmic-comp-config = { path = "../cosmic-comp/cosmic-comp-config" }
rust-embed = "8.11.0"
rust-embed-utils = "8.11.0"
rustc-hash = "2.1"
@ -58,14 +59,14 @@ tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
tracing-log = "0.2.0"
tokio = { version = "1.49.0", features = ["full"] }
# cosmic-config = { path = "../libcosmic/cosmic-config" }
cosmic-config = { git = "https://github.com/pop-os/libcosmic" }
cosmic-config = { path = "../libcosmic/cosmic-config" }
serde = { version = "1.0.228", features = ["derive"] }
zlink = "0.5.0"
[profile.release]
# opt-level = 3
# panic = "abort"
# lto = "thin"
opt-level = 1
opt-level = 3
panic = "abort"
lto = "thin"
[workspace.metadata.cargo-machete]
ignored = ["libcosmic"]
@ -74,20 +75,52 @@ ignored = ["libcosmic"]
# cosmic-config = { path = "../libcosmic/cosmic-config" }
# libcosmic = { path = "../libcosmic" }
# iced_futures = { path = "../libcosmic/iced/futures" }
# cosmic-config = { git = "https://github.com/pop-os/libcosmic//" }
# libcosmic = { git = "https://github.com/pop-os/libcosmic//" }
# iced_futures = { git = "https://github.com/pop-os/libcosmic//" }
# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "theme-v2" }
# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "theme-v2" }
# iced_futures = { git = "https://github.com/pop-os/libcosmic//", branch = "theme-v2" }
# [patch."https://github.com/pop-os/winit.git"]
# winit = { git = "https://github.com/rust-windowing/winit.git", rev = "241b7a80bba96c91fa3901729cd5dec66abb9be4" }
# winit = { path = "../winit" }
[patch."https://github.com/pop-os/libcosmic"]
cosmic-config = { path = "/home/lionel/Projets/COSMIC/libcosmic/cosmic-config" }
cosmic-theme = { path = "/home/lionel/Projets/COSMIC/libcosmic/cosmic-theme" }
iced = { path = "/home/lionel/Projets/COSMIC/libcosmic/iced" }
iced_accessibility = { path = "/home/lionel/Projets/COSMIC/libcosmic/iced/accessibility" }
iced_core = { path = "/home/lionel/Projets/COSMIC/libcosmic/iced/core" }
iced_futures = { path = "/home/lionel/Projets/COSMIC/libcosmic/iced/futures" }
iced_graphics = { path = "/home/lionel/Projets/COSMIC/libcosmic/iced/graphics" }
iced_renderer = { path = "/home/lionel/Projets/COSMIC/libcosmic/iced/renderer" }
iced_runtime = { path = "/home/lionel/Projets/COSMIC/libcosmic/iced/runtime" }
iced_tiny_skia = { path = "/home/lionel/Projets/COSMIC/libcosmic/iced/tiny_skia" }
iced_wgpu = { path = "/home/lionel/Projets/COSMIC/libcosmic/iced/wgpu" }
iced_widget = { path = "/home/lionel/Projets/COSMIC/libcosmic/iced/widget" }
iced_winit = { path = "/home/lionel/Projets/COSMIC/libcosmic/iced/winit" }
[patch."https://github.com/smithay/client-toolkit.git"]
sctk = { package = "smithay-client-toolkit", version = "0.20.0" }
[patch."https://github.com/pop-os/cosmic-protocols"]
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols//", branch = "main" }
cosmic-client-toolkit = { git = "https://github.com/pop-os/cosmic-protocols//", branch = "main" }
cosmic-protocols = { path = "/home/lionel/Projets/COSMIC/cosmic-protocols" }
cosmic-client-toolkit = { path = "/home/lionel/Projets/COSMIC/cosmic-protocols/client-toolkit" }
[patch."https://github.com/pop-os/cosmic-panel"]
cosmic-panel-config = { path = "/home/lionel/Projets/COSMIC/cosmic-panel/cosmic-panel-config" }
xdg-shell-wrapper-config = { path = "/home/lionel/Projets/COSMIC/cosmic-panel/xdg-shell-wrapper-config" }
[patch."https://github.com/pop-os/cosmic-notifications"]
cosmic-notifications-config = { path = "/home/lionel/Projets/COSMIC/cosmic-notifications/cosmic-notifications-config" }
cosmic-notifications-util = { path = "/home/lionel/Projets/COSMIC/cosmic-notifications/cosmic-notifications-util" }
[patch."https://github.com/pop-os/cosmic-settings"]
cosmic-settings-a11y-manager-subscription = { path = "/home/lionel/Projets/COSMIC/cosmic-settings/subscriptions/a11y-manager" }
cosmic-settings-accessibility-subscription = { path = "/home/lionel/Projets/COSMIC/cosmic-settings/subscriptions/accessibility" }
cosmic-settings-airplane-mode-subscription = { path = "/home/lionel/Projets/COSMIC/cosmic-settings/subscriptions/airplane-mode" }
cosmic-settings-daemon-subscription = { path = "/home/lionel/Projets/COSMIC/cosmic-settings/subscriptions/settings-daemon" }
cosmic-settings-upower-subscription = { path = "/home/lionel/Projets/COSMIC/cosmic-settings/subscriptions/upower" }
[patch."https://github.com/pop-os/cosmic-settings/"]
cosmic-settings-airplane-mode-subscription = { path = "/home/lionel/Projets/COSMIC/cosmic-settings/subscriptions/airplane-mode" }
[patch."https://github.com/pop-os/cosmic-text.git"]
cosmic-text = { path = "../cosmic-text" }
# [patch.'https://github.com/pop-os/dbus-settings-bindings']
# cosmic-dbus-networkmanager = { path = "../dbus-settings-bindings/networkmanager" }

View file

@ -1,6 +1,6 @@
[package]
name = "cosmic-app-list"
version = "1.0.2"
version = "1.3.0"
edition = "2024"
license = "GPL-3.0-only"
@ -13,13 +13,13 @@ futures.workspace = true
i18n-embed.workspace = true
i18n-embed-fl.workspace = true
image = { version = "0.25.9", default-features = false }
libcosmic.workspace = true
cosmic.workspace = true
memmap2 = "0.9.10"
fastrand = "2.3.0"
rust-embed.workspace = true
rustix.workspace = true
rustc-hash.workspace = true
switcheroo-control = { git = "https://github.com/pop-os/dbus-settings-bindings" }
switcheroo-control = { path = "../../dbus-settings-bindings/switcheroo-control" }
tokio.workspace = true
tracing-log.workspace = true
tracing-subscriber.workspace = true

View file

@ -1,10 +1,10 @@
[package]
name = "cosmic-app-list-config"
version = "1.0.2"
version = "1.3.0"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libcosmic.workspace = true
cosmic.workspace = true
serde = { workspace = true, features = ["derive"] }

View file

@ -1,6 +1,6 @@
cosmic-app-list = دُرج التطبيقات
pin = ثبِّت في درج التطبيقات
quit = أنهِ
quit = إنهِ
quit-all = أنهِ الكل
new-window = نافذة جديدة
run = شغِّل

View file

@ -0,0 +1,8 @@
new-window = Nova Finestra
quit = Surt
run = Executa
run-on = Executa a { $gpu }
quit-all = Surt de totes
run-on-default = (Per defecte)
cosmic-app-list = Safata d'Aplicacions
pin = Ancora a la safata

View file

@ -1,5 +1,5 @@
cosmic-app-list = Panel aplikací
pin = Připnout do panelu aplikací
cosmic-app-list = Oblast aplikací
pin = Připnout do oblasti aplikací
quit = Ukončit
quit-all = Ukončit vše
new-window = Nové okno

View file

@ -0,0 +1,8 @@
run = Kør
run-on = Kør på { $gpu }
run-on-default = (Standard)
cosmic-app-list = Programbakke
new-window = Nyt Vindue
quit = Afslut
quit-all = Forlad Alle
pin = Fastgør til app-skuffe

View file

@ -1 +1,8 @@
new-window = Νέο παράθυρο
quit = Τερματισμός
cosmic-app-list = Περιοχή εφαρμογών
run = Εκτέλεση
run-on = Εκτέλεση με { $gpu }
quit-all = Έξοδος από όλα
run-on-default = (Προεπιλογή)
pin = Καρφίτσωμα στην περιοχή εφαρμογών

View file

@ -0,0 +1,8 @@
cosmic-app-list = App Tray
new-window = New Window
quit = Quit
run = Run
run-on = Run on { $gpu }
quit-all = Quit All
run-on-default = (Default)
pin = Pin to app tray

View file

@ -5,4 +5,15 @@ quit-all = Quit All
new-window = New Window
run = Run
run-on = Run on {$gpu}
run-on-default = (Default)
run-on-default = (Default)
edit-launcher = Edit launcher
launcher-name = Name
launcher-command = Command
launcher-icon = Icon
launcher-icon-theme = Theme
launcher-icon-search = Search icons
launcher-icon-catalog-loading = Loading icons
launcher-icon-catalog-empty = No icons
launcher-icons = icons
save = Save
cancel = Cancel

View file

@ -1,8 +1,8 @@
cosmic-app-list = سینی برنامه‌ها
pin = سنجاق کردن به سینی برنامه‌ها
quit = بستن
quit-all = بستن همه
cosmic-app-list = نوار برنامه‌ها
pin = افزودن به نوار برنامه‌ها
quit = خروج
quit-all = خروج از همه
new-window = پنجره جدید
run = اجرا
run-on = اجرا روی {$gpu}
run-on-default = (پیش‌فرض)
run-on = اجرا با { $gpu }
run-on-default = (پیش‌فرض)

View file

@ -3,6 +3,17 @@ pin = Épingler à la barre d'applis
quit = Quitter
quit-all = Tout quitter
new-window = Nouvelle fenêtre
run = Lancer
run = Exécuter
run-on = Lancer avec { $gpu }
run-on-default = (Défaut)
edit-launcher = Modifier le lanceur
launcher-name = Nom
launcher-command = Commande
launcher-icon = Icône
launcher-icon-theme = Thème
launcher-icon-search = Rechercher une icône
launcher-icon-catalog-loading = Chargement des icônes
launcher-icon-catalog-empty = Aucune icône
launcher-icons = icônes
save = Enregistrer
cancel = Annuler

View file

@ -0,0 +1,8 @@
quit = Pechar
run = Executar
cosmic-app-list = Bandexa de aplicacións
pin = Fixar na bandexa de aplicacións
quit-all = Quitar todo
new-window = Nova Xanela
run-on = Executar en { $gpu }
run-on-default = (Por defecto)

View file

@ -1,8 +1,8 @@
cosmic-app-list = ऐप ट्रे
pin = ऐप ट्रे में पिन करें
quit = बंद करें
quit = बाहर जाएँ
quit-all = सभी बंद करें
new-window = नई विंडो
run = चलाएँ
run-on = {$gpu} पर चलाएँ
run-on = { $gpu } पर चलाएँ
run-on-default = (डिफ़ॉल्ट)

View file

@ -1,8 +1,8 @@
cosmic-app-list = Barra applicazioni
cosmic-app-list = Area applicazioni
pin = Fissa nella barra delle applicazioni
quit = Esci
quit-all = Chiudi tutto
new-window = Nuova finestra
run = Esegui
run-on = Esegui usando la GPU: { $gpu }
run-on-default = (default)
run-on = Esegui su { $gpu }
run-on-default = (Predefinito)

View file

@ -1,5 +1,5 @@
quit = Шығу
run = Іске қосу
run = Орындау
run-on = { $gpu } арқылы іске қосу
run-on-default = (Әдепкі)
cosmic-app-list = Қолданбалар сөресі

View file

@ -0,0 +1,8 @@
run = Bixebitîne
run-on = Li ser { $gpu } bixebitîne
run-on-default = (Berdest)
pin = Bo destgeha sepanan bi dar bixe
quit = Biqedîne
quit-all = Hemûyan biqedîne
new-window = Çarçoveya nû
cosmic-app-list = Destgeha sepanan

View file

@ -1,5 +1,5 @@
new-window = Naujas Langas
quit = Išeiti
quit = Išjungti
run = Paleisti
run-on = Paleisti naudojantis { $gpu }
run-on-default = (Numatytas)

View file

@ -0,0 +1 @@
cosmic-app-list = Cinta d’aplicacions

View file

@ -1,4 +1,4 @@
quit = ਬਾਹਰ
quit = ਸਮਾਪਤ
cosmic-app-list = ਐਪ ਟਰੇ
pin = ਐਪ ਟਰੇ ਵਿੱਚ ਟੰਗੋ
quit-all = ਸਭ ਵਿੱਚੋਂ ਬਾਹਰ

View file

@ -1,6 +1,6 @@
cosmic-app-list = Zasobnik aplikacji
pin = Przypnij do zasobnika aplikacji
quit = Zamknij
quit = Zakończ
quit-all = Zamknij wszystkie
new-window = Nowe okno
run = Uruchom

View file

@ -1,6 +1,6 @@
cosmic-app-list = Bandeja de aplicativos
pin = Fixar na bandeja de aplicativos
quit = Sair
quit = Encerrar
quit-all = Sair de Todos
new-window = Nova Janela
run = Executar

View file

@ -1,8 +1,8 @@
cosmic-app-list = Область приложений
pin = Закрепить на панели приложений
quit = Выйти
quit = Завершить
quit-all = Завершить все
new-window = Новое окно
run = Запустить
run = Выполнить
run-on = Запустить на { $gpu }
run-on-default = (По умолчанию)

View file

@ -0,0 +1,2 @@
run-on = { $gpu } ᱨᱮ ᱫᱟᱹᱲ ᱟᱭ
run-on-default = (ᱢᱩᱞ)

View file

@ -0,0 +1,8 @@
new-window = Нови прозор
quit = Изађи
run = Покрени
run-on = Покрени на { $gpu }
quit-all = Изађи из свега
run-on-default = (подразумевано)
cosmic-app-list = Системска касета
pin = Закачи у системску касету

View file

@ -1,8 +1,8 @@
cosmic-app-list = Панель застосунків
pin = Закріпити
quit = Вийти
quit = Завершити
quit-all = Закрити всі
new-window = Нове вікно
run = Запустити
run = Виконати
run-on = Запустити на { $gpu }
run-on-default = (Основна)

View file

@ -1,8 +1,8 @@
cosmic-app-list = 應用程式匣盤
pin = 釘選至應用程式匣盤
quit = 退出
cosmic-app-list = 應用程式托盤
pin = 釘選至應用程式托盤
quit = 結束
quit-all = 全部退出
new-window = 新建視窗
new-window = 新增視窗
run = 執行
run-on = 在 { $gpu } 上執行
run-on-default = (預設)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,440 @@
use std::{
cmp::Ordering,
collections::{HashMap, HashSet, VecDeque},
fs,
path::{Component, Path, PathBuf},
};
const FALLBACK_THEMES: &[&str] = &["Cosmic", "hicolor", "gnome", "Yaru"];
const MAX_THEME_CHAIN: usize = 24;
const MAX_SCAN_DEPTH: usize = 5;
const MAX_CATALOG_ENTRIES: usize = 2_500;
#[derive(Debug, Clone)]
pub struct IconCatalog {
pub theme: String,
pub entries: Vec<IconCatalogEntry>,
pub truncated: bool,
}
#[derive(Debug, Clone)]
pub struct IconCatalogEntry {
pub name: String,
}
#[derive(Debug, Clone, Eq, PartialEq)]
struct CandidateRank {
preferred: u8,
category: u8,
symbolic: u8,
theme_depth: usize,
extension: u8,
}
impl Ord for CandidateRank {
fn cmp(&self, other: &Self) -> Ordering {
(
self.preferred,
self.category,
self.symbolic,
self.theme_depth,
self.extension,
)
.cmp(&(
other.preferred,
other.category,
other.symbolic,
other.theme_depth,
other.extension,
))
}
}
impl PartialOrd for CandidateRank {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Clone)]
struct Candidate {
name: String,
rank: CandidateRank,
}
pub async fn load_icon_catalog(theme: String, preferred_icon: String) -> IconCatalog {
build_icon_catalog(theme, preferred_icon)
}
fn build_icon_catalog(theme: String, preferred_icon: String) -> IconCatalog {
let theme = if theme.trim().is_empty() {
"Cosmic".to_string()
} else {
theme
};
let preferred_name = icon_name_from_value(preferred_icon.trim());
let theme_chain = theme_chain(&theme);
let mut candidates = HashMap::new();
for (theme_depth, theme_name) in theme_chain.iter().enumerate() {
for theme_dir in theme_dirs(theme_name) {
scan_icon_tree(
&theme_dir,
theme_depth,
preferred_name.as_deref(),
&mut candidates,
);
}
}
for pixmap_dir in pixmap_dirs() {
scan_icon_tree(
&pixmap_dir,
theme_chain.len() + 1,
preferred_name.as_deref(),
&mut candidates,
);
}
let mut entries = candidates.into_values().collect::<Vec<_>>();
entries.sort_by(|a, b| a.rank.cmp(&b.rank).then_with(|| a.name.cmp(&b.name)));
let truncated = entries.len() > MAX_CATALOG_ENTRIES;
entries.truncate(MAX_CATALOG_ENTRIES);
IconCatalog {
theme,
entries: entries
.into_iter()
.map(|candidate| IconCatalogEntry {
name: candidate.name,
})
.collect(),
truncated,
}
}
fn theme_chain(theme: &str) -> Vec<String> {
let mut queue = VecDeque::from([theme.to_string()]);
let mut seen = HashSet::new();
let mut chain = Vec::new();
while let Some(theme_name) = queue.pop_front() {
let key = theme_name.to_ascii_lowercase();
if !seen.insert(key) {
continue;
}
chain.push(theme_name.clone());
if chain.len() >= MAX_THEME_CHAIN {
break;
}
for parent in read_theme_inherits(&theme_name) {
queue.push_back(parent);
}
}
for fallback in FALLBACK_THEMES {
if chain.len() >= MAX_THEME_CHAIN {
break;
}
let key = fallback.to_ascii_lowercase();
if seen.insert(key) {
chain.push((*fallback).to_string());
}
}
chain
}
fn read_theme_inherits(theme: &str) -> Vec<String> {
theme_dirs(theme)
.into_iter()
.find_map(|dir| fs::read_to_string(dir.join("index.theme")).ok())
.map(|contents| parse_inherits(&contents))
.unwrap_or_default()
}
fn parse_inherits(contents: &str) -> Vec<String> {
let mut in_icon_theme = false;
for raw_line in contents.lines() {
let line = raw_line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if line.starts_with('[') && line.ends_with(']') {
in_icon_theme = line == "[Icon Theme]";
continue;
}
if in_icon_theme && let Some(value) = line.strip_prefix("Inherits=") {
return value
.split(',')
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
.collect();
}
}
Vec::new()
}
fn scan_icon_tree(
root: &Path,
theme_depth: usize,
preferred_name: Option<&str>,
candidates: &mut HashMap<String, Candidate>,
) {
let mut stack = vec![(root.to_path_buf(), 0usize)];
while let Some((dir, depth)) = stack.pop() {
if depth > MAX_SCAN_DEPTH {
continue;
}
let Ok(entries) = fs::read_dir(&dir) else {
continue;
};
for entry in entries.filter_map(Result::ok) {
let path = entry.path();
let Ok(file_type) = entry.file_type() else {
continue;
};
if file_type.is_dir() {
stack.push((path, depth + 1));
} else if (file_type.is_file() || file_type.is_symlink())
&& let Some(candidate) = candidate_from_path(&path, theme_depth, preferred_name)
{
insert_candidate(candidates, candidate);
}
}
}
}
fn insert_candidate(candidates: &mut HashMap<String, Candidate>, candidate: Candidate) {
let key = candidate.name.to_ascii_lowercase();
match candidates.get_mut(&key) {
Some(existing) if candidate.rank < existing.rank => {
*existing = candidate;
}
None => {
candidates.insert(key, candidate);
}
_ => {}
}
}
fn candidate_from_path(
path: &Path,
theme_depth: usize,
preferred_name: Option<&str>,
) -> Option<Candidate> {
let extension = extension_rank(path)?;
let name = path.file_stem()?.to_str()?.trim();
if name.is_empty() {
return None;
}
let symbolic = u8::from(name.ends_with("-symbolic") || path_has_component(path, "symbolic"));
let preferred = u8::from(preferred_name != Some(name));
Some(Candidate {
name: name.to_string(),
rank: CandidateRank {
preferred,
category: category_rank(path),
symbolic,
theme_depth,
extension,
},
})
}
fn extension_rank(path: &Path) -> Option<u8> {
match path.extension()?.to_str()?.to_ascii_lowercase().as_str() {
"svg" => Some(0),
"png" => Some(1),
"xpm" => Some(2),
_ => None,
}
}
fn category_rank(path: &Path) -> u8 {
for component in path.components().filter_map(component_str) {
match component {
"apps" | "applications" => return 0,
"categories" => return 1,
"places" => return 2,
"devices" => return 3,
"mimetypes" => return 4,
"actions" => return 5,
"status" => return 6,
_ => {}
}
}
7
}
fn path_has_component(path: &Path, needle: &str) -> bool {
path.components()
.filter_map(component_str)
.any(|component| component == needle)
}
fn component_str(component: Component<'_>) -> Option<&str> {
component.as_os_str().to_str()
}
fn icon_name_from_value(value: &str) -> Option<String> {
if value.is_empty() {
return None;
}
let path = Path::new(value);
if value.contains('/') {
return path
.file_stem()
.and_then(|name| name.to_str())
.map(ToOwned::to_owned);
}
path.file_stem()
.and_then(|name| name.to_str())
.map(ToOwned::to_owned)
.or_else(|| Some(value.to_string()))
}
fn theme_dirs(theme: &str) -> Vec<PathBuf> {
icon_base_dirs()
.into_iter()
.map(|base| base.join(theme))
.filter(|path| path.is_dir())
.collect()
}
fn icon_base_dirs() -> Vec<PathBuf> {
let mut dirs = Vec::new();
if let Some(home) = std::env::home_dir() {
push_existing_unique(&mut dirs, home.join(".icons"));
}
if let Some(data_home) = xdg_data_home() {
push_existing_unique(&mut dirs, data_home.join("icons"));
}
for data_dir in xdg_data_dirs() {
push_existing_unique(&mut dirs, data_dir.join("icons"));
}
dirs
}
fn pixmap_dirs() -> Vec<PathBuf> {
let mut dirs = Vec::new();
if let Some(data_home) = xdg_data_home() {
push_existing_unique(&mut dirs, data_home.join("pixmaps"));
}
for data_dir in xdg_data_dirs() {
push_existing_unique(&mut dirs, data_dir.join("pixmaps"));
}
push_existing_unique(&mut dirs, PathBuf::from("/usr/share/pixmaps"));
dirs
}
fn xdg_data_home() -> Option<PathBuf> {
std::env::var_os("XDG_DATA_HOME")
.filter(|value| !value.is_empty())
.map(PathBuf::from)
.or_else(|| std::env::home_dir().map(|home| home.join(".local/share")))
}
fn xdg_data_dirs() -> Vec<PathBuf> {
std::env::var_os("XDG_DATA_DIRS")
.filter(|value| !value.is_empty())
.map(|value| std::env::split_paths(&value).collect())
.unwrap_or_else(|| {
vec![
PathBuf::from("/usr/local/share"),
PathBuf::from("/usr/share"),
]
})
}
fn push_existing_unique(dirs: &mut Vec<PathBuf>, path: PathBuf) {
if path.exists() && !dirs.iter().any(|existing| existing == &path) {
dirs.push(path);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_inherits_only_from_icon_theme_section() {
let contents = r#"
[Other]
Inherits=Wrong
[Icon Theme]
Name=Demo
Inherits=Cosmic, hicolor , Adwaita
"#;
assert_eq!(parse_inherits(contents), ["Cosmic", "hicolor", "Adwaita"]);
}
#[test]
fn normalizes_icon_names_from_plain_names_and_paths() {
assert_eq!(icon_name_from_value("firefox").as_deref(), Some("firefox"));
assert_eq!(
icon_name_from_value("/usr/share/icons/hicolor/scalable/apps/firefox.svg").as_deref(),
Some("firefox")
);
}
#[test]
fn keeps_the_best_duplicate_candidate() {
let mut candidates = HashMap::new();
insert_candidate(
&mut candidates,
Candidate {
name: "demo".to_string(),
rank: CandidateRank {
preferred: 1,
category: 7,
symbolic: 1,
theme_depth: 4,
extension: 2,
},
},
);
insert_candidate(
&mut candidates,
Candidate {
name: "demo".to_string(),
rank: CandidateRank {
preferred: 0,
category: 0,
symbolic: 0,
theme_depth: 0,
extension: 0,
},
},
);
assert_eq!(candidates["demo"].rank.preferred, 0);
assert_eq!(candidates["demo"].rank.category, 0);
}
}

View file

@ -0,0 +1,378 @@
// Copyright 2026 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use std::{
env,
ffi::OsString,
io::ErrorKind,
path::{Path, PathBuf},
};
use tokio::fs;
#[derive(Clone, Debug)]
pub struct LauncherEditRequest {
pub current_app_id: String,
pub source_path: PathBuf,
pub name: String,
pub exec: String,
pub icon: String,
pub terminal: bool,
pub replace_localized_name: bool,
pub disable_dbus_activation: bool,
}
#[derive(Clone, Debug)]
pub struct LauncherEditResult {
pub old_app_id: String,
pub new_app_id: String,
pub path: PathBuf,
}
#[derive(Clone, Debug)]
struct ValidLauncherEdit {
current_app_id: String,
source_path: PathBuf,
name: String,
exec: String,
icon: String,
terminal: bool,
replace_localized_name: bool,
disable_dbus_activation: bool,
}
pub fn validate_launcher_fields(name: &str, exec: &str, icon: &str) -> Result<(), String> {
validate_required("Name", name)?;
validate_required("Command", exec)?;
validate_optional("Icon", icon)?;
Ok(())
}
pub async fn save_launcher_edit(
request: LauncherEditRequest,
) -> Result<LauncherEditResult, String> {
let request = validate_request(request)?;
let (new_app_id, target_path) =
target_launcher_path(&request.current_app_id, &request.source_path)?;
let source = read_source_desktop_entry(&request).await?;
let rendered = render_editable_desktop_entry(&source, &request);
let Some(parent) = target_path.parent() else {
return Err("Desktop file target has no parent directory".to_string());
};
fs::create_dir_all(parent)
.await
.map_err(|err| format!("Could not create applications directory: {err}"))?;
let tmp_path = temporary_path(&target_path)?;
fs::write(&tmp_path, rendered)
.await
.map_err(|err| format!("Could not write temporary desktop file: {err}"))?;
if let Err(err) = fs::rename(&tmp_path, &target_path).await {
let _ = fs::remove_file(&tmp_path).await;
return Err(format!("Could not install desktop file: {err}"));
}
Ok(LauncherEditResult {
old_app_id: request.current_app_id,
new_app_id,
path: target_path,
})
}
fn validate_request(request: LauncherEditRequest) -> Result<ValidLauncherEdit, String> {
let name = validate_required("Name", &request.name)?;
let exec = validate_required("Command", &request.exec)?;
let icon = validate_optional("Icon", &request.icon)?;
Ok(ValidLauncherEdit {
current_app_id: request.current_app_id,
source_path: request.source_path,
name,
exec,
icon,
terminal: request.terminal,
replace_localized_name: request.replace_localized_name,
disable_dbus_activation: request.disable_dbus_activation,
})
}
fn validate_required(label: &str, value: &str) -> Result<String, String> {
let value = validate_optional(label, value)?;
if value.is_empty() {
return Err(format!("{label} cannot be empty"));
}
Ok(value)
}
fn validate_optional(label: &str, value: &str) -> Result<String, String> {
if value.contains('\0') || value.contains('\n') || value.contains('\r') {
return Err(format!("{label} cannot contain line breaks"));
}
Ok(value.trim().to_string())
}
async fn read_source_desktop_entry(request: &ValidLauncherEdit) -> Result<String, String> {
if request.source_path.as_os_str().is_empty() {
return Ok(minimal_desktop_entry());
}
match fs::read_to_string(&request.source_path).await {
Ok(source) => Ok(source),
Err(err) if err.kind() == ErrorKind::NotFound => Ok(minimal_desktop_entry()),
Err(err) => Err(format!("Could not read source desktop file: {err}")),
}
}
fn minimal_desktop_entry() -> String {
"[Desktop Entry]\nType=Application\n".to_string()
}
fn user_applications_dir() -> Result<PathBuf, String> {
if let Some(xdg_data_home) = env::var_os("XDG_DATA_HOME").filter(|value| !value.is_empty()) {
return Ok(PathBuf::from(xdg_data_home).join("applications"));
}
let Some(home) = env::var_os("HOME").filter(|value| !value.is_empty()) else {
return Err("Neither XDG_DATA_HOME nor HOME is set".to_string());
};
Ok(PathBuf::from(home).join(".local/share/applications"))
}
fn target_launcher_path(app_id: &str, source_path: &Path) -> Result<(String, PathBuf), String> {
let applications_dir = user_applications_dir()?;
if source_path.starts_with(&applications_dir) {
return Ok((app_id.to_string(), source_path.to_path_buf()));
}
let desktop_id = normalize_desktop_id(app_id)?;
Ok((
desktop_id.clone(),
applications_dir.join(format!("{desktop_id}.desktop")),
))
}
fn normalize_desktop_id(app_id: &str) -> Result<String, String> {
if app_id.contains('\0') || app_id.contains('\n') || app_id.contains('\r') {
return Err("Desktop ID cannot contain line breaks".to_string());
}
let desktop_id = app_id
.chars()
.map(|c| if c == '/' { '-' } else { c })
.collect::<String>();
if desktop_id.trim().is_empty() {
return Err("Desktop ID cannot be empty".to_string());
}
Ok(desktop_id)
}
fn temporary_path(target_path: &Path) -> Result<PathBuf, String> {
let Some(file_name) = target_path.file_name() else {
return Err("Desktop file target has no filename".to_string());
};
let mut tmp_file_name = OsString::from(".");
tmp_file_name.push(file_name);
tmp_file_name.push(format!(".tmp-{}", std::process::id()));
Ok(target_path.with_file_name(tmp_file_name))
}
fn render_editable_desktop_entry(source: &str, request: &ValidLauncherEdit) -> String {
let mut updates = vec![
("Type", "Application".to_string()),
("Name", request.name.clone()),
("Exec", request.exec.clone()),
("Icon", request.icon.clone()),
(
"Terminal",
if request.terminal { "true" } else { "false" }.to_string(),
),
("X-COSMIC-UserEditable", "true".to_string()),
("X-COSMIC-SourceDesktopId", request.current_app_id.clone()),
];
if !request.source_path.as_os_str().is_empty() {
updates.push((
"X-COSMIC-SourceDesktopPath",
request.source_path.to_string_lossy().to_string(),
));
}
if request.disable_dbus_activation {
updates.push(("DBusActivatable", "false".to_string()));
}
set_desktop_entry_keys(
source,
&updates,
request.replace_localized_name,
request.disable_dbus_activation,
)
}
fn set_desktop_entry_keys(
source: &str,
updates: &[(&str, String)],
replace_localized_name: bool,
remove_try_exec: bool,
) -> String {
let mut out = Vec::new();
let mut seen = vec![false; updates.len()];
let mut in_desktop_entry = false;
let mut saw_desktop_entry = false;
for line in source.lines() {
if let Some(section) = section_name(line) {
if in_desktop_entry {
insert_missing_keys(&mut out, updates, &seen);
}
in_desktop_entry = section == "Desktop Entry";
saw_desktop_entry |= in_desktop_entry;
if in_desktop_entry {
seen.fill(false);
}
out.push(line.to_string());
continue;
}
if in_desktop_entry && let Some(key) = desktop_entry_key(line) {
if replace_localized_name && key.starts_with("Name[") {
continue;
}
if remove_try_exec && key == "TryExec" {
continue;
}
if let Some(index) = updates
.iter()
.position(|(update_key, _)| *update_key == key)
{
out.push(format!("{}={}", updates[index].0, updates[index].1));
seen[index] = true;
continue;
}
}
out.push(line.to_string());
}
if in_desktop_entry {
insert_missing_keys(&mut out, updates, &seen);
}
if !saw_desktop_entry {
let mut with_desktop_entry = Vec::with_capacity(out.len() + updates.len() + 2);
with_desktop_entry.push("[Desktop Entry]".to_string());
insert_missing_keys(
&mut with_desktop_entry,
updates,
&vec![false; updates.len()],
);
if !out.is_empty() {
with_desktop_entry.push(String::new());
with_desktop_entry.extend(out);
}
out = with_desktop_entry;
}
let mut rendered = out.join("\n");
rendered.push('\n');
rendered
}
fn insert_missing_keys(out: &mut Vec<String>, updates: &[(&str, String)], seen: &[bool]) {
for (index, (key, value)) in updates.iter().enumerate() {
if !seen[index] {
out.push(format!("{key}={value}"));
}
}
}
fn section_name(line: &str) -> Option<&str> {
let trimmed = line.trim();
trimmed
.strip_prefix('[')
.and_then(|value| value.strip_suffix(']'))
}
fn desktop_entry_key(line: &str) -> Option<&str> {
let line = line.trim_start();
if line.starts_with('#') || line.starts_with(';') {
return None;
}
line.split_once('=').map(|(key, _)| key.trim_end())
}
#[cfg(test)]
mod tests {
use super::*;
fn request() -> ValidLauncherEdit {
ValidLauncherEdit {
current_app_id: "org.example.App".to_string(),
source_path: PathBuf::from("/usr/share/applications/org.example.App.desktop"),
name: "Example".to_string(),
exec: "example --new".to_string(),
icon: "example-custom".to_string(),
terminal: false,
replace_localized_name: true,
disable_dbus_activation: true,
}
}
#[test]
fn updates_desktop_entry_without_dropping_action_groups() {
let source = "\
[Desktop Entry]
Type=Application
Name=Old
Name[fr]=Ancien
Exec=old
TryExec=old
Icon=old
DBusActivatable=true
Actions=new-window;
[Desktop Action new-window]
Name=New Window
Exec=old --new-window
";
let rendered = render_editable_desktop_entry(source, &request());
assert!(rendered.contains("Name=Example\n"));
assert!(!rendered.contains("Name[fr]="));
assert!(rendered.contains("Exec=example --new\n"));
assert!(rendered.contains("Icon=example-custom\n"));
assert!(rendered.contains("DBusActivatable=false\n"));
assert!(!rendered.contains("TryExec="));
assert!(rendered.contains("[Desktop Action new-window]\n"));
assert!(rendered.contains("Exec=old --new-window\n"));
}
#[test]
fn preserves_localized_names_when_name_is_unchanged() {
let mut edit = request();
edit.replace_localized_name = false;
let rendered = render_editable_desktop_entry(
"[Desktop Entry]\nName=Example\nName[fr]=Exemple\nExec=old\n",
&edit,
);
assert!(rendered.contains("Name=Example\n"));
assert!(rendered.contains("Name[fr]=Exemple\n"));
}
}

View file

@ -2,6 +2,8 @@
// SPDX-License-Identifier: GPL-3.0-only
mod app;
mod icon_catalog;
mod launcher_edit;
mod localize;
mod wayland_handler;
mod wayland_subscription;

View file

@ -388,7 +388,10 @@ impl CaptureData {
},
)
.unwrap();
self.conn.flush().unwrap();
if let Err(err) = self.conn.flush() {
tracing::error!("Wayland flush failed during screencopy session create: {err}");
return None;
}
let formats = session
.wait_while(|data| data.formats.is_none())
@ -437,7 +440,10 @@ impl CaptureData {
session: capture_session.clone(),
},
);
self.conn.flush().unwrap();
if let Err(err) = self.conn.flush() {
tracing::error!("Wayland flush failed during screencopy capture: {err}");
return None;
}
// TODO: wait for server to release buffer?
let res = session
@ -709,7 +715,10 @@ pub(crate) fn wayland_handler(
if app_data.exit {
break;
}
event_loop.dispatch(None, &mut app_data).unwrap();
if let Err(err) = event_loop.dispatch(None, &mut app_data) {
tracing::error!("Wayland event loop terminated: {err}");
break;
}
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "cosmic-applet-a11y"
version = "1.0.2"
version = "1.3.0"
edition = "2024"
[dependencies]
@ -9,7 +9,7 @@ cctk.workspace = true
cosmic-protocols.workspace = true
i18n-embed-fl.workspace = true
i18n-embed.workspace = true
libcosmic.workspace = true
cosmic.workspace = true
rust-embed.workspace = true
tokio.workspace = true
tracing-log.workspace = true
@ -17,9 +17,7 @@ tracing-subscriber.workspace = true
tracing.workspace = true
[dependencies.cosmic-settings-a11y-manager-subscription]
git = "https://github.com/pop-os/cosmic-settings"
# path = "../../cosmic-settings/subscriptions/a11y-manager"
path = "../../cosmic-settings/subscriptions/a11y-manager"
[dependencies.cosmic-settings-accessibility-subscription]
git = "https://github.com/pop-os/cosmic-settings"
# path = "../../cosmic-settings/subscriptions/accessibility"
path = "../../cosmic-settings/subscriptions/accessibility"

View file

@ -0,0 +1,6 @@
magnifier = Lupa
screen-reader = Narrador
invert-colors = Colors invertits
settings = Configuració d'accessibilitat...
filter-colors = Filtres de colors
high-contrast = Mode d'alt contrast

View file

@ -1,6 +1,6 @@
screen-reader = Čtečka obrazovky
magnifier = Lupa
invert-colors = Převrátit barvy
invert-colors = Inverze barev
settings = Nastavení přístupnosti...
filter-colors = Filtrovat barvy
filter-colors = Filtr barev
high-contrast = Vysoký kontrast

View file

@ -0,0 +1,6 @@
screen-reader = Skærmlæser
invert-colors = Invertér farver
high-contrast = Høj kontrast
filter-colors = Filtrér farver
settings = Tilgængelighedsindstillinger...
magnifier = Forstørrelsesglas

View file

@ -1,6 +1,6 @@
screen-reader = Bildschirmleser
magnifier = Vergrößerungsglas
invert-colors = Farben invertieren
settings = Barrierefreiheitseinstellungen…
settings = Barrierefreiheitseinstellungen ...
filter-colors = Farben filtern
high-contrast = Hoher Kontrast

View file

@ -0,0 +1,6 @@
screen-reader = Ανάγνωση οθόνης
invert-colors = Αναστροφή χρωμάτων
high-contrast = Υψηλή αντίθεση
settings = Ρυθμίσεις προσβασιμότητας...
magnifier = Μεγεθυντικός φακός
filter-colors = Φίλτρο χρωμάτων

View file

@ -0,0 +1,6 @@
screen-reader = Screen reader
invert-colors = Invert colours
high-contrast = High contrast
filter-colors = Filter colours
settings = Accessibility settings...
magnifier = Magnifier

View file

@ -1,6 +1,6 @@
screen-reader = صفحه‌خوان
magnifier = ذره‌بین
invert-colors = وارونه‌سازی رنگ‌ها
invert-colors = وارونه کردن رنگ‌ها
settings = تنظیمات دسترسی‌پذیری...
filter-colors = فیلتر رنگ‌ها
high-contrast = کنتراست بالا

View file

@ -0,0 +1,6 @@
screen-reader = Lector de pantalla
magnifier = Lupa
invert-colors = Invertir cores
settings = Configuración de accesibilidade...
filter-colors = Filtro de cores
high-contrast = Alto contraste

View file

@ -1,4 +1,6 @@
screen-reader = स्क्रीन रीडर
filter-colors = रंग फ़िल्टर
settings = सुलभता सेटिंग..।
magnifier = आवर्धक
filter-colors = रंग फ़िल्टर करें
settings = एक्सेसिबिलिटी सेटिंग्स..।
magnifier = मैग्नीफायर
high-contrast = उच्च कंट्रास्ट
invert-colors = रंग उल्टे करें

View file

@ -2,5 +2,5 @@ screen-reader = Lettore dello schermo
magnifier = Lente di ingrandimento
invert-colors = Inverti colori
settings = Impostazioni accessibilità...
filter-colors = Filtra colori
filter-colors = Filtri colore
high-contrast = Contrasto elevato

View file

@ -2,5 +2,5 @@ screen-reader = スクリーンリーダー
magnifier = 拡大鏡
settings = アクセシビリティ設定…
invert-colors = 色を反転
filter-colors = 色のフィルター
filter-colors = カラーフィルター
high-contrast = ハイコントラスト

View file

@ -0,0 +1,6 @@
screen-reader = Xwînerê dîmenderê
magnifier = Mezinker
invert-colors = Rengan berevajî bike
settings = Sazkariyên gihîştinê...
filter-colors = Rengan parzûn bike
high-contrast = Dijbiriya bilind

View file

@ -0,0 +1,2 @@
screen-reader = ᱥᱠᱨᱤᱱ ᱨᱤᱰᱟᱨ
invert-colors = ᱨᱚᱝ ᱩᱞᱴᱟᱹ ᱪᱷᱚ

View file

@ -0,0 +1,6 @@
screen-reader = Читач екрана
invert-colors = Обрни боје
high-contrast = Високи контраст
filter-colors = Филтрирај боје
settings = Подешавања приступачности...
magnifier = Увеличавач

View file

@ -26,10 +26,9 @@ use cosmic::{
};
use cosmic_settings_a11y_manager_subscription::{
self as cosmic_a11y_manager, AccessibilityEvent, AccessibilityRequest, ColorFilter,
AccessibilityEvent, AccessibilityRequest, ColorFilter,
};
use cosmic_settings_accessibility_subscription::{self as accessibility};
use std::sync::LazyLock;
use tokio::sync::mpsc::UnboundedSender;
pub fn run() -> cosmic::iced::Result {
@ -52,6 +51,7 @@ struct CosmicA11yApplet {
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
enum Message {
TogglePopup,
CloseRequested(window::Id),
@ -133,20 +133,27 @@ impl cosmic::Application for CosmicA11yApplet {
}
Message::TogglePopup => {
if let Some(p) = self.popup.take() {
return destroy_popup(p);
return cosmic::surface::surface_task(cosmic::surface::action::destroy_popup(
p,
));
} else {
let new_id = window::Id::unique();
self.popup.replace(new_id);
return cosmic::surface::surface_task(cosmic::surface::action::app_popup(
|_| Default::default(),
|app: &mut CosmicA11yApplet| {
let new_id = window::Id::unique();
app.popup.replace(new_id);
let popup_settings = self.core.applet.get_popup_settings(
self.core.main_window_id().unwrap(),
new_id,
Some((1, 1)),
let popup_settings = app.core.applet.get_popup_settings(
app.core.main_window_id().unwrap(),
new_id,
Some((1, 1)),
None,
None,
);
popup_settings
},
None,
None,
);
return get_popup(popup_settings);
));
}
}
Message::CloseRequested(id) => {

View file

@ -2,13 +2,12 @@
// SPDX-License-Identifier: GPL-3.0-only
use anyhow;
use cctk::sctk::reexports::calloop::{self, channel::SyncSender};
use cctk::sctk::reexports::calloop::{self};
use cosmic::iced::{
self, Subscription,
futures::{self, SinkExt, StreamExt, channel::mpsc},
futures::{self, SinkExt},
stream,
};
use cosmic_protocols::a11y::v1::client::cosmic_a11y_manager_v1::Filter;
use cosmic_settings_a11y_manager_subscription::{
self as thread, AccessibilityEvent, AccessibilityRequest,
};

View file

@ -1,15 +1,15 @@
[package]
name = "cosmic-applet-audio"
version = "1.0.2"
version = "1.3.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
futures.workspace = true
i18n-embed-fl.workspace = true
i18n-embed.workspace = true
libcosmic.workspace = true
mpris2-zbus = { git = "https://github.com/pop-os/dbus-settings-bindings" }
# mpris2-zbus = { path = "../../dbus-settings-bindings/mpris2" }
cosmic.workspace = true
mpris2-zbus = { path = "../../dbus-settings-bindings/mpris2" }
rust-embed.workspace = true
serde.workspace = true
tokio.workspace = true
@ -19,7 +19,9 @@ tracing.workspace = true
url = "2"
urlencoding = "2.1.3"
zbus.workspace = true
zlink.workspace = true
intmap = "3.1.3"
[dependencies.cosmic-settings-sound-subscription]
git = "https://github.com/pop-os/cosmic-settings"
# path = "../../cosmic-settings/subscriptions/sound"
[dependencies.cosmic-settings-audio-client]
path = "../../cosmic-settings-daemon/audio-client"
features = ["codec"]

View file

@ -0,0 +1,7 @@
output = Sortida
show-media-controls = Mostra els controls multimèdia a la barra
disconnected = PulseAudio desconnectat
no-device = No s'ha seleccionat cap dispositiu
input = Entrada
unknown-artist = Desconegut
sound-settings = Configuració de so...

View file

@ -0,0 +1,7 @@
show-media-controls = Vis mediekontrol på panel
disconnected = PulseAudio Afbrudt
no-device = Ingen enhed valgt
input = Indgang
output = Udgang
unknown-artist = Ukendt
sound-settings = Lydindstillinger...

View file

@ -1,7 +1,7 @@
output = Ausgabe
input = Eingabe
show-media-controls = Mediensteuerung im Panel anzeigen
sound-settings = Klang-Einstellungen...
sound-settings = Klang-Einstellungen ...
disconnected = PulseAudio getrennt
no-device = Kein Gerät ausgewählt
unknown-artist = Unbekannt

View file

@ -0,0 +1,7 @@
output = Έξοδος
show-media-controls = Εμφάνιση στοιχείων ελέγχου πολυμέσων στη γραμμή συστήματος
disconnected = Το PulseAudio αποσυνδέθηκε
no-device = Καμία επιλεγμένη συσκευή
input = Είσοδος
unknown-artist = Άγνωστος
sound-settings = Ρυθμίσεις ήχου...

View file

@ -0,0 +1,7 @@
show-media-controls = Show media controls on panel
disconnected = PulseAudio Disconnected
no-device = No device selected
input = Input
output = Output
unknown-artist = Unknown
sound-settings = Sound Settings...

View file

@ -0,0 +1,7 @@
output = Saída
input = Entrada
show-media-controls = Mostrar os controis multimedia no panel
sound-settings = Axustes do son...
disconnected = PulseAudio Desconectado
no-device = Ningún dipositivo seleccionado
unknown-artist = Descoñecido

View file

@ -2,6 +2,6 @@ output = Kimenet
input = Bemenet
show-media-controls = Médiavezérlők megjelenítése a panelen
sound-settings = Hangbeállítások…
disconnected = PulseAudio nincs csatlakozva
no-device = Nincs kiválasztott eszköz
disconnected = A PulseAudio-kapcsolat megszakadt
no-device = Nincs eszköz kiválasztva
unknown-artist = Ismeretlen

View file

@ -4,4 +4,4 @@ show-media-controls = Mostra controlli multimediali nel pannello in alto
sound-settings = Impostazioni audio...
disconnected = PulseAudio Disconnesso
no-device = Nessun dispositivo selezionato
unknown-artist = Sconosciuto
unknown-artist = Sconosciuto

View file

@ -0,0 +1,7 @@
output = Deran
input = Ketan
unknown-artist = Nenas
sound-settings = Sazkariyên dengê...
disconnected = Girêdana PulseAudio qut bû
no-device = Tu amûr nehatine hilbijartin
show-media-controls = Kontrolên mediyayê li ser destgehê nîşan bide

View file

@ -4,4 +4,4 @@ no-device = Nepasirinktas įrenginys
input = Įvestis
output = Išvestis
unknown-artist = Nežinomas atlikėjas
sound-settings = Garso Nustatymai...
sound-settings = Garso Nuostatos...

View file

@ -0,0 +1 @@
output = ᱟᱨᱡᱟᱣ

View file

@ -0,0 +1,7 @@
show-media-controls = Прикажи управљања медијима на траци
disconnected = Пулс-аудио откачен
no-device = Ниједан уређај није изабран
input = Улаз
output = Излаз
unknown-artist = Непознато
sound-settings = Подешавања звука...

View file

@ -2,12 +2,13 @@
// SPDX-License-Identifier: GPL-3.0-only
mod localize;
mod model;
mod mouse_area;
use crate::localize::localize;
use config::{AudioAppletConfig, amplification_sink, amplification_source};
use cosmic::{
Element, Renderer, Task, Theme, app,
Apply, Element, Renderer, Task, Theme, app,
applet::{
column as applet_column,
cosmic_panel_config::PanelAnchor,
@ -18,18 +19,29 @@ use cosmic::{
cosmic_config::CosmicConfigEntry,
cosmic_theme::Spacing,
iced::{
self, Alignment, Length, Subscription,
self, Alignment, Length, Rectangle, Subscription,
futures::StreamExt,
widget::{self, column, row, slider},
window,
},
surface, theme,
widget::{Row, button, container, divider, icon, space, text, toggler},
widget::{
Row, button, container, divider, icon,
rectangle_tracker::{RectangleTracker, RectangleUpdate, rectangle_tracker_subscription},
space, text, toggler,
},
};
use cosmic_settings_sound_subscription as css;
use iced::platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup};
use cosmic_settings_audio_client::{self as audio_client, CosmicAudioProxy};
use futures::SinkExt;
use mpris_subscription::{MprisRequest, MprisUpdate};
use mpris2_zbus::player::PlaybackStatus;
use std::{
sync::{
Arc,
atomic::{self, AtomicU32},
},
time::Duration,
};
mod config;
mod mpris_subscription;
@ -50,10 +62,16 @@ pub struct Audio {
core: cosmic::app::Core,
/// Track the applet's popup window.
popup: Option<window::Id>,
/// The model from cosmic-settings for managing pipewire devices.
model: css::Model,
/// Varlink connection to `com.system76.CosmicSettings.Audio`.
audio_client: Option<audio_client::Client>,
/// Known audio device state
model: model::Model,
/// Whether to expand the revealer of a source or sink device.
is_open: IsOpen,
/// Used to debounce sink volume updates.
applied_sink_volume: Arc<AtomicU32>,
/// Used to deboune source volume updates.
applied_source_volume: Arc<AtomicU32>,
/// Max slider volume for the sink device, as determined by the amplification property.
max_sink_volume: u32,
/// Max slider volume for the source device, as determined by the amplification property.
@ -68,12 +86,14 @@ pub struct Audio {
player_status: Option<mpris_subscription::PlayerStatus>,
/// Used to request an activation token for opening cosmic-settings.
token_tx: Option<calloop::channel::Sender<TokenRequest>>,
rectangle_tracker: Option<RectangleTracker<u32>>,
rectangle: Option<iced::Rectangle>,
}
impl Audio {
fn output_icon_name(&self) -> &'static str {
let volume = self.model.sink_volume;
let mute = self.model.sink_mute;
let volume = self.model.active_sink.volume;
let mute = self.model.active_sink.mute;
if mute || volume == 0 {
"audio-volume-muted-symbolic"
} else if volume < 33 {
@ -88,8 +108,8 @@ impl Audio {
}
fn input_icon_name(&self) -> &'static str {
let volume = self.model.source_volume;
let mute = self.model.source_mute;
let volume = self.model.active_source.volume;
let mute = self.model.active_source.mute;
if mute || volume == 0 {
"microphone-sensitivity-muted-symbolic"
} else if volume < 33 {
@ -100,6 +120,21 @@ impl Audio {
"microphone-sensitivity-high-symbolic"
}
}
/// Refresh the volume ceilings and slider breakpoints from the
/// amplification config.
///
/// Must run at startup, not only when the popup opens: the
/// scroll-to-adjust handler clamps against `max_sink_volume`, which
/// `Default` leaves at 0. Until the popup had been opened once, every
/// wheel event therefore resolved to `SetSinkVolume(0)`.
fn refresh_volume_limits(&mut self) {
(self.max_sink_volume, self.sink_breakpoints) =
if amplification_sink() { (150, &[100][..]) } else { (100, &[][..]) };
(self.max_source_volume, self.source_breakpoints) =
if amplification_source() { (150, &[100][..]) } else { (100, &[][..]) };
}
}
#[derive(Debug, PartialEq, Eq, Default)]
@ -110,27 +145,31 @@ enum IsOpen {
Input,
}
#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
pub enum Message {
Ignore,
SetSinkVolume(u32),
SetSourceVolume(u32),
ToggleSinkMute,
ToggleSourceMute,
SetDefaultSink(usize),
SetDefaultSource(usize),
OutputToggle,
InputToggle,
TogglePopup,
/// Connection to `com.system76.CosmicSettings`.
Client(Arc<audio_client::Client>),
CloseRequested(window::Id),
ToggleMediaControlsInTopPanel(bool),
ConfigChanged(AudioAppletConfig),
Ignore,
InputToggle,
Mpris(mpris_subscription::MprisUpdate),
MprisRequest(MprisRequest),
Token(TokenUpdate),
OpenSettings,
Subscription(css::Message),
OutputToggle,
ReattachClient(Arc<audio_client::Client>),
Rectangle(RectangleUpdate<u32>),
SetDefaultSink(usize),
SetDefaultSource(usize),
SetSinkVolume(u32),
SetSourceVolume(u32),
Subscription(audio_client::Event),
Surface(surface::Action),
ToggleMediaControlsInTopPanel(bool),
TogglePopup,
ToggleSinkMute,
ToggleSourceMute,
Token(TokenUpdate),
}
// TODO
@ -242,19 +281,13 @@ impl cosmic::Application for Audio {
const APP_ID: &'static str = "com.system76.CosmicAppletAudio";
fn init(core: cosmic::app::Core, _flags: ()) -> (Self, app::Task<Message>) {
let mut model = css::Model::default();
model.unplugged_text = "Unplugged".into();
model.hd_audio_text = "HD Audio".into();
model.usb_audio_text = "USB Audio".into();
let mut app = Self {
core,
..Default::default()
};
app.refresh_volume_limits();
(
Self {
core,
model,
..Default::default()
},
Task::none(),
)
(app, Task::none())
}
fn core(&self) -> &cosmic::app::Core {
@ -271,35 +304,53 @@ impl cosmic::Application for Audio {
fn update(&mut self, message: Message) -> app::Task<Message> {
match message {
Message::Rectangle(u) => match u {
RectangleUpdate::Rectangle(r) => {
self.rectangle = Some(r.1);
}
RectangleUpdate::Init(tracker) => {
self.rectangle_tracker.replace(tracker);
}
},
Message::ReattachClient(client) => {
if let Some(client) = Arc::into_inner(client) {
self.audio_client = Some(client);
}
}
Message::Ignore => {}
Message::TogglePopup => {
if let Some(p) = self.popup.take() {
return destroy_popup(p);
return cosmic::surface::surface_task(cosmic::surface::action::destroy_popup(
p,
));
} else {
let new_id = window::Id::unique();
self.popup.replace(new_id);
return cosmic::surface::surface_task(cosmic::surface::action::app_popup(
|_| Default::default(),
|app: &mut Self| {
let new_id = window::Id::unique();
app.popup.replace(new_id);
(self.max_sink_volume, self.sink_breakpoints) = if amplification_sink() {
(150, &[100][..])
} else {
(100, &[][..])
};
app.refresh_volume_limits();
(self.max_source_volume, self.source_breakpoints) = if amplification_source() {
(150, &[100][..])
} else {
(100, &[][..])
};
let popup_settings = self.core.applet.get_popup_settings(
self.core.main_window_id().unwrap(),
new_id,
let mut popup_settings = app.core.applet.get_popup_settings(
app.core.main_window_id().unwrap(),
new_id,
None,
None,
None,
);
if let Some(r) = app.rectangle {
popup_settings.positioner.anchor_rect = Rectangle {
x: r.x as i32,
y: r.y as i32,
width: r.width as i32,
height: r.height as i32,
};
}
popup_settings
},
None,
None,
None,
);
return get_popup(popup_settings);
));
}
}
@ -318,52 +369,90 @@ impl cosmic::Application for Audio {
}
}
Message::Subscription(message) => {
return self
.model
.update(message)
.map(|message| Message::Subscription(message).into());
self.model.update(message);
}
Message::SetDefaultSink(pos) => {
return self
.model
.set_default_sink(pos)
.map(|message| Message::Subscription(message).into());
if let Some(&pos) = self.model.sinks.sorted_index.get(pos)
&& let Some(&node_id) = self.model.sinks.id.get(pos as usize)
&& let Some(client) = self.audio_client.as_mut()
{
futures::executor::block_on(async {
_ = client.conn.set_default(node_id, true).await;
});
}
}
Message::SetDefaultSource(pos) => {
return self
.model
.set_default_source(pos)
.map(|message| Message::Subscription(message).into());
if let Some(&pos) = self.model.sources.sorted_index.get(pos)
&& let Some(&node_id) = self.model.sources.id.get(pos as usize)
&& let Some(client) = self.audio_client.as_mut()
{
futures::executor::block_on(async {
_ = client.conn.set_default(node_id, true).await;
});
}
}
Message::ToggleSinkMute => self.model.toggle_sink_mute(),
Message::ToggleSinkMute => {
if let Some(ref mut client) = self.audio_client {
futures::executor::block_on(async {
_ = client.conn.sink_mute_toggle().await;
});
}
}
Message::ToggleSourceMute => self.model.toggle_source_mute(),
Message::ToggleSourceMute => {
if let Some(ref mut client) = self.audio_client {
futures::executor::block_on(async {
_ = client.conn.source_mute_toggle().await;
});
}
}
Message::SetSinkVolume(volume) => {
return self
.model
.set_sink_volume(volume)
.map(|message| Message::Subscription(message).into());
self.applied_sink_volume
.store(volume, atomic::Ordering::Relaxed);
self.model.active_sink.volume = volume;
self.model.active_sink.volume_text = volume.to_string();
if let Some(mut client) = self.audio_client.take() {
let volume = Arc::clone(&self.applied_sink_volume);
return cosmic::Task::future(async move {
tokio::time::sleep(Duration::from_millis(128)).await;
_ = client
.conn
.set_sink_volume(volume.load(atomic::Ordering::Relaxed))
.await;
Message::ReattachClient(Arc::new(client)).into()
});
}
}
Message::SetSourceVolume(volume) => {
return self
.model
.set_source_volume(volume)
.map(|message| Message::Subscription(message).into());
self.applied_source_volume
.store(volume, atomic::Ordering::Relaxed);
self.model.active_source.volume = volume;
self.model.active_source.volume_text = volume.to_string();
if let Some(mut client) = self.audio_client.take() {
let volume = Arc::clone(&self.applied_source_volume);
return cosmic::Task::future(async move {
tokio::time::sleep(Duration::from_millis(128)).await;
_ = client
.conn
.set_sink_volume(volume.load(atomic::Ordering::Relaxed))
.await;
Message::ReattachClient(Arc::new(client)).into()
});
}
}
Message::ToggleMediaControlsInTopPanel(enabled) => {
self.config.show_media_controls_in_top_panel = enabled;
if let Ok(helper) =
cosmic::cosmic_config::Config::new(Self::APP_ID, AudioAppletConfig::VERSION)
&& let Err(err) = self.config.write_entry(&helper)
{
if let Err(err) = self.config.write_entry(&helper) {
tracing::error!(?err, "Error writing config");
}
tracing::error!(?err, "Error writing config");
}
}
Message::CloseRequested(id) => {
@ -461,6 +550,12 @@ impl cosmic::Application for Audio {
cosmic::app::Action::Surface(a),
));
}
Message::Client(client) => {
if let Some(client) = Arc::into_inner(client) {
self.audio_client = Some(client);
self.model = model::Model::default();
}
}
}
Task::none()
@ -476,7 +571,50 @@ impl cosmic::Application for Audio {
}),
mpris_subscription::mpris_subscription(0).map(Message::Mpris),
activation_token_subscription(0).map(Message::Token),
Subscription::run(|| css::watch().map(Message::Subscription)),
Subscription::run(|| {
iced::stream::channel(
1,
move |mut emitter: futures::channel::mpsc::Sender<_>| async move {
loop {
let mut client = match audio_client::connect().await {
Ok(client) => client,
Err(why) => {
if let zlink::Error::Io(ref why) = why
&& why.kind() == std::io::ErrorKind::NotFound
{
tracing::error!(
"cosmic-settings-daemon varlink service not found."
);
} else {
tracing::error!(
?why,
"failed to connect to cosmic-settings's varlink service"
);
}
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
continue;
}
};
if let Ok(Ok(mut stream)) = client.recv_events().await {
_ = emitter.send(Message::Client(Arc::new(client))).await;
while let Some(message) = stream.next().await {
match message {
Ok(event) => {
_ = emitter.send(Message::Subscription(event)).await;
}
Err(why) => {
tracing::error!(?why, "event error");
}
}
}
}
}
},
)
}),
rectangle_tracker_subscription(0).map(|update| Message::Rectangle(update.1)),
])
}
@ -487,63 +625,94 @@ impl cosmic::Application for Audio {
.icon_button(self.output_icon_name())
.on_press_down(Message::TogglePopup);
const WHEEL_STEP: f32 = 5.0; // 5% per wheel event
const WHEEL_STEP: f32 = 5.0; // 5% par cran logique
// Wayland axis_v120 envoie un cran physique en rafale de plusieurs
// ScrollDelta::Pixels (5–8 events ~15–20px), pour ~120px par cran. On
// accumule ces sub-events dans un thread_local et on n'émet qu'au
// passage d'un seuil — sinon `signum()` faisait croire que chaque
// sub-event = un cran, et un seul cran physique faisait chuter le
// volume jusqu'à 0 ("coupe le son").
const PIXEL_THRESHOLD: f32 = 15.0; // px par cran logique
std::thread_local! {
static PIXEL_ACC: std::cell::Cell<f32> = const { std::cell::Cell::new(0.0) };
}
let btn = crate::mouse_area::MouseArea::new(btn).on_mouse_wheel(|delta| {
let scroll_vector = match delta {
iced::mouse::ScrollDelta::Lines { y, .. } => y.signum() * WHEEL_STEP, // -1/0/1
iced::mouse::ScrollDelta::Pixels { y, .. } => y.signum(), // -1/0/1
iced::mouse::ScrollDelta::Lines { y, .. } => {
PIXEL_ACC.with(|a| a.set(0.0));
y * WHEEL_STEP
}
iced::mouse::ScrollDelta::Pixels { y, .. } => {
PIXEL_ACC.with(|acc_cell| {
let acc = acc_cell.get() + y;
let steps = (acc / PIXEL_THRESHOLD).trunc();
acc_cell.set(acc - steps * PIXEL_THRESHOLD);
steps * WHEEL_STEP
})
}
};
if scroll_vector == 0.0 {
return Message::Ignore;
}
let new_volume = (self.model.sink_volume as f64 + (scroll_vector as f64))
let new_volume = (self.model.active_sink.volume as f64 + (scroll_vector as f64))
.clamp(0.0, self.max_sink_volume as f64);
Message::SetSinkVolume(new_volume as u32)
Message::SetSinkVolume(new_volume.round() as u32)
});
let mut has_playback_buttons = false;
let playback_buttons = (!self.core.applet.suggested_bounds.as_ref().is_some_and(|c| {
// if we have a configure for width and height, we're in a overflow popup
c.width > 0. && c.height > 0.
}))
.then(|| self.playback_buttons());
self.core
.applet
.autosize_window(
if let Some(playback_buttons) = playback_buttons
&& !playback_buttons.is_empty()
{
match self.core.applet.anchor {
PanelAnchor::Left | PanelAnchor::Right => Element::from(
applet_column::Column::with_children(playback_buttons)
.push(btn)
.align_x(Alignment::Center)
.height(Length::Shrink)
// TODO configurable variable from the panel?
.spacing(
-(self.core.applet.suggested_padding(true).0 as f32)
* self.core.applet.padding_overlap,
),
let mut ret = if let Some(playback_buttons) = playback_buttons
&& !playback_buttons.is_empty()
{
has_playback_buttons = true;
Element::from(match self.core.applet.anchor {
PanelAnchor::Left | PanelAnchor::Right => Element::from(
applet_column::Column::with_children(playback_buttons)
.push(btn)
.align_x(Alignment::Center)
.height(Length::Shrink)
// TODO configurable variable from the panel?
.spacing(
-(self.core.applet.suggested_padding(true).0 as f32)
* self.core.applet.padding_overlap,
),
PanelAnchor::Top | PanelAnchor::Bottom => {
applet_row::Row::with_children(playback_buttons)
.push(btn)
.align_y(Alignment::Center)
.width(Length::Shrink)
// TODO configurable variable from the panel?
.spacing(
-(self.core.applet.suggested_padding(true).0 as f32)
* self.core.applet.padding_overlap,
)
.into()
}
}
} else {
btn.into()
},
)
.into()
),
PanelAnchor::Top | PanelAnchor::Bottom => {
applet_row::Row::with_children(playback_buttons)
.push(btn)
.align_y(Alignment::Center)
.width(Length::Shrink)
// TODO configurable variable from the panel?
.spacing(
-(self.core.applet.suggested_padding(true).0 as f32)
* self.core.applet.padding_overlap,
)
.into()
}
})
} else {
btn.into()
};
if let Some(tracker) = self.rectangle_tracker.as_ref()
&& has_playback_buttons
{
ret = tracker.container(0, ret).into()
}
if !self.core.applet.suggested_bounds.as_ref().is_some_and(|c| {
// if we have a configure for width and height, we're in a overflow popup
c.width > 0. && c.height > 0.
}) {
ret = self.core.applet.autosize_window(ret).into();
}
ret
}
fn view_window(&self, _id: window::Id) -> Element<'_, Message> {
@ -551,19 +720,21 @@ impl cosmic::Application for Audio {
space_xxs, space_s, ..
} = theme::active().cosmic().spacing;
let sink = self
let sink: Option<&str> = self
.model
.active_sink()
.and_then(|pos| self.model.sinks().get(pos));
let source = self
.sinks
.active()
.map(|pos| self.model.sinks.sorted_display[pos].as_ref());
let source: Option<&str> = self
.model
.active_source()
.and_then(|pos| self.model.sources().get(pos));
.sources
.active()
.map(|pos| self.model.sources.sorted_display[pos].as_ref());
let mut audio_content = {
let output_slider = slider(
0..=self.max_sink_volume,
self.model.sink_volume,
self.model.active_sink.volume,
Message::SetSinkVolume,
)
.width(Length::FillPortion(5))
@ -571,7 +742,7 @@ impl cosmic::Application for Audio {
let input_slider = slider(
0..=self.max_source_volume,
self.model.source_volume,
self.model.active_source.volume,
Message::SetSourceVolume,
)
.width(Length::FillPortion(5))
@ -590,7 +761,7 @@ impl cosmic::Application for Audio {
.line_height(24)
.on_press(Message::ToggleSinkMute),
output_slider,
container(text(&self.model.sink_volume_text).size(16))
container(text(&self.model.active_sink.volume_text).size(16))
.width(Length::FillPortion(1))
.align_x(Alignment::End)
]
@ -609,7 +780,7 @@ impl cosmic::Application for Audio {
.line_height(24)
.on_press(Message::ToggleSourceMute),
input_slider,
container(text(&self.model.source_volume_text).size(16))
container(text(&self.model.active_source.volume_text).size(16))
.width(Length::FillPortion(1))
.align_x(Alignment::End)
]
@ -624,7 +795,7 @@ impl cosmic::Application for Audio {
Some(sink) => sink.to_owned(),
None => fl!("no-device"),
},
self.model.sinks(),
&self.model.sinks.sorted_display,
Message::OutputToggle,
Message::SetDefaultSink,
),
@ -635,7 +806,7 @@ impl cosmic::Application for Audio {
Some(source) => source.to_owned(),
None => fl!("no-device"),
},
self.model.sources(),
&self.model.sources.sorted_display,
Message::InputToggle,
Message::SetDefaultSource,
)
@ -691,19 +862,15 @@ impl cosmic::Application for Audio {
}
if let Some(play) = self.is_play() {
control_elements.push(
button::icon(
icon::from_name(if play { PLAY } else { PAUSE })
.size(32)
.symbolic(true),
)
.extra_small()
.class(cosmic::theme::Button::AppletIcon)
.on_press(if play {
Message::MprisRequest(MprisRequest::Play)
} else {
Message::MprisRequest(MprisRequest::Pause)
})
.into(),
button::icon(icon::from_name(if play { PLAY } else { PAUSE }).symbolic(true))
.extra_small()
.class(cosmic::theme::Button::AppletIcon)
.on_press(if play {
Message::MprisRequest(MprisRequest::Play)
} else {
Message::MprisRequest(MprisRequest::Pause)
})
.into(),
);
}
if let Some(go_next) = self.go_next(32) {
@ -754,20 +921,21 @@ impl cosmic::Application for Audio {
}
}
fn revealer(
fn revealer<'a>(
open: bool,
title: String,
selected: String,
devices: &[String],
devices: &'a [Arc<str>],
toggle: Message,
mut change: impl FnMut(usize) -> Message + 'static,
) -> widget::Column<'static, Message, crate::Theme, Renderer> {
) -> widget::Column<'a, Message, crate::Theme, Renderer> {
if open {
devices.iter().cloned().enumerate().fold(
devices.iter().enumerate().fold(
column![revealer_head(open, title, selected, toggle)].width(Length::Fill),
|col, (id, name)| {
move |col, (id, name)| {
col.push(
menu_button(text::body(name))
text::body(name.as_ref())
.apply(menu_button)
.on_press(change(id))
.width(Length::Fill)
.padding([8, 48]),
@ -785,9 +953,9 @@ fn revealer_head(
selected: String,
toggle: Message,
) -> cosmic::widget::Button<'static, Message> {
menu_button(column![
text::body(title).width(Length::Fill),
text::caption(selected),
])
.on_press(toggle)
cosmic::widget::column::with_capacity(2)
.push(text::body(title).width(Length::Fill))
.push(text::caption(selected))
.apply(menu_button)
.on_press(toggle)
}

View file

@ -0,0 +1,393 @@
// Copyright 2026 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use std::sync::Arc;
use cosmic_settings_audio_client::{self as audio_client, Availability, RouteInfo};
use intmap::IntMap;
pub type DeviceId = u32;
pub type NodeId = u32;
#[derive(Debug, Default)]
pub struct Model {
pub device_routes: IntMap<DeviceId, Vec<RouteInfo>>,
pub node_devices: IntMap<NodeId, Option<u32>>,
pub sinks: Nodes,
pub sources: Nodes,
pub active_sink: ActiveNode,
pub active_source: ActiveNode,
pub default_sink: Option<NodeId>,
pub default_source: Option<NodeId>,
}
#[derive(Debug, Default)]
pub struct Nodes {
active: Option<usize>,
pub sorted_display: Box<[Arc<str>]>,
pub sorted_index: Box<[u16]>,
pub balance: Vec<Option<f32>>,
pub card_profile_device: Vec<Option<u32>>,
pub description: Vec<String>,
pub devices: Vec<Option<NodeId>>,
pub display: Vec<Arc<str>>,
pub mute: Vec<bool>,
pub name: Vec<String>,
pub id: Vec<NodeId>,
pub volume: Vec<u32>,
}
impl Nodes {
pub fn active(&self) -> Option<usize> {
self.active.and_then(|active| {
self.sorted_index
.iter()
.position(|idx| *idx as usize == active)
})
}
pub fn dropdown_sort(&mut self) {
let mut enumerated_displays = self
.display
.clone()
.into_iter()
.enumerate()
.map(|(index, display)| (index as u16, display))
.collect::<Vec<_>>();
enumerated_displays.sort_by_key(|v| v.1.clone());
let (indexes, displays): (Vec<_>, Vec<_>) = enumerated_displays.into_iter().unzip();
self.sorted_display = displays.into_boxed_slice();
self.sorted_index = indexes.into_boxed_slice();
}
pub fn remove(&mut self, node_id: u32) -> bool {
let Some(pos) = self.id.iter().position(|id| node_id == *id) else {
return false;
};
self.balance.remove(pos);
self.card_profile_device.remove(pos);
self.description.remove(pos);
self.devices.remove(pos);
self.display.remove(pos);
self.mute.remove(pos);
self.name.remove(pos);
self.id.remove(pos);
self.volume.remove(pos);
self.dropdown_sort();
if self.active == Some(pos) {
self.active = None;
}
true
}
}
#[derive(Debug, Default)]
pub struct ActiveNode {
pub volume_text: String,
pub volume: u32,
pub mute: bool,
}
impl Model {
pub fn update(&mut self, event: audio_client::Event) {
tracing::debug!(?event, "update");
match event {
audio_client::Event::NodeMute(node_id, mute) => {
if let Some(pos) = self.sinks.id.iter().position(|id| node_id == *id) {
self.sinks.mute[pos] = mute;
if self.sinks.active == Some(pos) {
self.active_sink.mute = mute;
}
} else if let Some(pos) = self.sources.id.iter().position(|id| node_id == *id) {
self.sources.mute[pos] = mute;
if self.sources.active == Some(pos) {
self.active_source.mute = mute;
}
}
}
audio_client::Event::NodeVolume(node_id, volume, balance) => {
if let Some(pos) = self.sinks.id.iter().position(|id| node_id == *id) {
self.sinks.volume[pos] = volume;
self.sinks.balance[pos] = balance;
if self.default_sink.as_ref().is_some_and(|&id| id == node_id)
&& let Some(pos) = self.sinks.active
{
self.active_sink.mute = self.sinks.mute[pos];
self.active_sink.volume = self.sinks.volume[pos];
self.active_sink.volume_text = self.active_sink.volume.to_string();
}
} else if let Some(pos) = self.sources.id.iter().position(|id| node_id == *id) {
self.sources.volume[pos] = volume;
self.sources.balance[pos] = balance;
if self
.default_source
.as_ref()
.is_some_and(|&id| id == node_id)
&& let Some(pos) = self.sources.active
{
self.active_source.mute = self.sources.mute[pos];
self.active_source.volume = self.sources.volume[pos];
self.active_source.volume_text = self.active_source.volume.to_string();
}
}
}
audio_client::Event::DefaultSink(node_id) => {
self.default_sink = Some(node_id);
if let Some(pos) = self.sinks.id.iter().position(|&id| id == node_id) {
self.sinks.active = Some(pos);
self.active_sink.mute = self.sinks.mute[pos];
self.active_sink.volume = self.sinks.volume[pos];
self.active_sink.volume_text = self.active_sink.volume.to_string();
}
}
audio_client::Event::DefaultSource(node_id) => {
self.default_source = Some(node_id);
if let Some(pos) = self.sources.id.iter().position(|&id| id == node_id) {
self.sources.active = Some(pos);
self.active_source.mute = self.sources.mute[pos];
self.active_source.volume = self.sources.volume[pos];
self.active_source.volume_text = self.active_source.volume.to_string();
}
}
audio_client::Event::Node(node_id, node) => {
self.node_devices.insert(node_id, node.device_id);
if node.is_sink {
let pos = if let Some(pos) = self.sinks.id.iter().position(|&id| id == node_id)
{
self.sinks.description[pos] = self.translate(&node.description);
self.sinks.name[pos] = node.name;
self.sinks.card_profile_device[pos] = node.card_profile_device;
pos
} else {
self.sinks.display.push(Arc::default());
self.sinks
.description
.push(self.translate(&node.description));
self.sinks.id.push(node_id);
self.sinks.volume.push(0);
self.sinks.balance.push(None);
self.sinks.mute.push(false);
self.sinks.name.push(node.name);
self.sinks.devices.push(node.device_id);
self.sinks
.card_profile_device
.push(node.card_profile_device);
self.sinks.id.len() - 1
};
self.sinks.display[pos] = node
.device_id
.zip(node.card_profile_device)
.and_then(|(device_id, node_card_profile_device)| {
let routes = self.device_routes.get(device_id)?;
for route in routes {
if matches!(route.availability, Availability::No) || !route.is_sink
{
continue;
}
if route.devices.contains(&node_card_profile_device) {
return Some(node_name(
&self.translate(&route.description),
&self.sinks.description[pos],
));
}
}
None
})
.unwrap_or_else(|| {
node_name(
&node.device_profile_description,
&self.sinks.description[pos],
)
});
self.sinks.dropdown_sort();
if let Some(default_node_id) = self.default_sink
&& default_node_id == node_id
{
self.sinks.active = Some(pos);
self.active_sink.mute = self.sinks.mute[pos];
self.active_sink.volume = self.sinks.volume[pos];
self.active_sink.volume_text = self.active_sink.volume.to_string();
}
} else {
let pos =
if let Some(pos) = self.sources.id.iter().position(|&id| id == node_id) {
self.sources.description[pos] = self.translate(&node.description);
self.sources.name[pos] = node.name;
self.sources.card_profile_device[pos] = node.card_profile_device;
pos
} else {
self.sources
.description
.push(self.translate(&node.description));
self.sources.display.push(Arc::default());
self.sources.id.push(node_id);
self.sources.volume.push(0);
self.sources.balance.push(None);
self.sources.mute.push(false);
self.sources.name.push(node.name);
self.sources.devices.push(node.device_id);
self.sources
.card_profile_device
.push(node.card_profile_device);
self.sources.id.len() - 1
};
if let Some(name) = node
.device_id
.zip(node.card_profile_device)
.map(|(device_id, node_card_profile_device)| {
let routes = self.device_routes.get(device_id)?;
for route in routes {
if route.is_sink || matches!(route.availability, Availability::No) {
continue;
}
if route.devices.contains(&node_card_profile_device) {
return Some(node_name(
&self.translate(&route.description),
&self.sources.description[pos],
));
}
}
None
})
.unwrap_or_else(|| {
Some(node_name(
&node.device_profile_description,
&self.sources.description[pos],
))
})
{
self.sources.display[pos] = name;
self.sources.dropdown_sort();
} else {
// Remove sources that are unplugged.
self.sources.remove(node_id);
return;
}
if let Some(default_node_id) = self.default_source
&& default_node_id == node_id
{
self.sources.active = Some(pos);
self.active_source.mute = self.sources.mute[pos];
self.active_source.volume = self.sources.volume[pos];
self.active_source.volume_text = self.active_source.volume.to_string();
}
}
}
audio_client::Event::ActiveRoute(device_id, _index, route) => {
self.update_device_names(device_id, &route);
}
audio_client::Event::Route(device_id, index, route) => {
let routes = self.device_routes.entry(device_id).or_default();
if index == 0 {
*routes = vec![route];
} else {
if routes.len() < index as usize + 1 {
let additional = (index as usize + 1) - routes.capacity();
routes.reserve_exact(additional);
routes.extend(std::iter::repeat_n(RouteInfo::default(), additional));
}
routes[index as usize] = route;
}
}
audio_client::Event::RemoveNode(node_id) => {
self.node_devices.remove(node_id);
if !self.sinks.remove(node_id) {
self.sources.remove(node_id);
}
}
audio_client::Event::RemoveDevice(device_id) => {
self.device_routes.remove(device_id);
}
_ => (),
}
}
fn update_device_names(&mut self, device_id: DeviceId, route: &RouteInfo) {
if matches!(route.availability, Availability::No) {
return;
}
let compatible_nodes = self.node_devices.iter().filter_map(|(node, &dev_id)| {
if dev_id? == device_id {
Some(node)
} else {
None
}
});
if route.is_sink {
for n_id in compatible_nodes {
let Some(pos) = self.sinks.id.iter().position(|&node| node == n_id) else {
continue;
};
let Some(card_profile_device) = self.sinks.card_profile_device[pos] else {
continue;
};
if route.devices.contains(&card_profile_device) {
self.sinks.display[pos] = node_name(
&self.translate(&route.description),
&self.sinks.description[pos],
);
self.sinks.dropdown_sort();
break;
}
}
} else {
for n_id in compatible_nodes {
let Some(pos) = self.sources.id.iter().position(|&node| node == n_id) else {
continue;
};
let Some(card_profile_device) = self.sources.card_profile_device[pos] else {
continue;
};
if route.devices.contains(&card_profile_device) {
self.sources.display[pos] = node_name(
&self.translate(&route.description),
&self.sources.description[pos],
);
self.sources.dropdown_sort();
break;
}
}
}
}
pub fn translate(&self, description: &str) -> String {
description
.replace("High Definition", "HD")
.replace("DisplayPort", "DP")
.replace("Controller", "")
}
}
fn node_name(route: &str, node: &str) -> Arc<str> {
if route.is_empty() {
node.to_owned()
} else {
[route, " - ", node].concat()
}
.into()
}

View file

@ -5,7 +5,7 @@ use cosmic::iced::core::Point;
use cosmic::iced::core::{
Clipboard, Element, Layout, Length, Rectangle, Shell, Size, Widget,
event::{self, Event},
event::Event,
layout, mouse, overlay, renderer, touch,
widget::{Operation, Tree, tree},
};
@ -307,6 +307,19 @@ fn update<Message: Clone, Theme, Renderer>(
shell: &mut Shell<'_, Message>,
state: &mut State,
) {
// Handle wheel events before the bounds check. The panel's nested
// compositor routes axis events to this surface only when the pointer
// is over the applet, but the cursor coordinates it sends may be in
// panel-local space rather than surface-local space. This makes
// cursor.is_over() unreliable for scroll events.
if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event {
if let Some(message) = widget.on_mouse_wheel.as_ref() {
shell.publish((message)(*delta));
shell.capture_event();
return;
}
}
if !cursor.is_over(layout.bounds()) {
if !state.is_out_of_bounds {
if widget
@ -416,12 +429,4 @@ fn update<Message: Clone, Theme, Renderer>(
}
}
}
if let Some(message) = widget.on_mouse_wheel.as_ref() {
if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event {
shell.publish((message)(*delta));
shell.capture_event();
return;
}
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "cosmic-applet-battery"
version = "1.0.2"
version = "1.3.0"
edition = "2024"
license = "GPL-3.0-only"
@ -12,7 +12,7 @@ drm = "0.14.1"
futures.workspace = true
i18n-embed-fl.workspace = true
i18n-embed.workspace = true
libcosmic.workspace = true
cosmic.workspace = true
rust-embed.workspace = true
rustc-hash.workspace = true
tokio.workspace = true
@ -24,9 +24,7 @@ zbus.workspace = true
serde.workspace = true
[dependencies.cosmic-settings-upower-subscription]
git = "https://github.com/pop-os/cosmic-settings"
# path = "../../cosmic-settings/subscriptions/upower"
path = "../../cosmic-settings/subscriptions/upower"
[dependencies.cosmic-settings-daemon-subscription]
git = "https://github.com/pop-os/cosmic-settings"
# path = "../../cosmic-settings/subscriptions/settings-daemon"
path = "../../cosmic-settings/subscriptions/settings-daemon"

View file

@ -0,0 +1,14 @@
power-settings = Strøm- og Batteriindstillinger...
hours = t
until-empty = indtil tom
battery-desc = Reduceret strømforbrug og ydeevne.
max-charge = Forøg dit batteris levetid, ved at angive en maksimal opladningsværdi på 80%
balanced = Balanceret
battery = Batteri
seconds = s
dgpu-running = Diskret GPU er aktiv og kan reducere batterilevetiden
performance = Høj Ydeevne
performance-desc = Høj ydeevne og strømforbrug.
minutes = m
balanced-desc = Standard ydeevne og batteriforbrug.
dgpu-applications = Applikationer som bruger { $gpu_name } diskret GPU

View file

@ -9,6 +9,6 @@ seconds = s
minutes = m
hours = h
until-empty = bis leer
power-settings = Energie- und Akkueinstellungen...
power-settings = Energie- und Akkueinstellungen ...
dgpu-running = Dedizierte GPU ist aktiv und kann die Akkulaufzeit reduzieren
dgpu-applications = Anwendungen, welche die dedizierte GPU { $gpu_name } nutzen

View file

@ -0,0 +1,14 @@
battery = Μπαταρία
power-settings = Ρυθμίσεις ενέργειας και μπαταρίας...
hours = ώ
until-empty = μέχρι την αποφόρτιση
battery-desc = Μειωμένη χρήση ενέργειας και επιδόσεις.
max-charge = Επεκτείνετε τη διάρκεια ζωής της μπαταρίας σας ορίζοντας ως μέγιστη τιμή φόρτισης το 80%
balanced = Ισορροπημένη
seconds = δ
dgpu-running = Η ανεξάρτητη GPU είναι ενεργή και μπορεί να μειώσει τη διάρκεια ζωής της μπαταρίας
performance = Υψηλές επιδόσεις
performance-desc = Υψηλές επιδόσεις και χρήση ενέργειας.
minutes = λ
balanced-desc = Τυπικές επιδόσεις και χρήση μπαταρίας.
dgpu-applications = Εφαρμογές που χρησιμοποιούν την ανεξάρτητη GPU { $gpu_name }

View file

@ -0,0 +1,14 @@
power-settings = Power & Battery settings...
hours = h
until-empty = until empty
battery-desc = Reduced power usage and performance.
max-charge = Increase the lifespan of your battery by setting a maximum charge value of 80%
balanced = Balanced
battery = Battery
seconds = s
dgpu-running = Discrete GPU is active and can reduce battery life
performance = High Performance
performance-desc = High performance and power usage.
minutes = m
balanced-desc = Standard performance and battery usage.
dgpu-applications = Applications using { $gpu_name } discrete GPU

View file

@ -0,0 +1,14 @@
battery = Batería
battery-desc = Reduce o consumo de enerxía e o rendemento.
balanced = Balanceado
balanced-desc = Rendemento e consumo de batería estándar.
performance = Alto rendemento
performance-desc = Alto rendemento e maior consumo de enerxía.
max-charge = Aumenta a vida útil da batería limitando a carga máxima ao 80 %
seconds = s
minutes = m
hours = h
until-empty = ata baleirar
power-settings = Axustes de enexía e batería...
dgpu-running = A GPU dedicada está activa e pode reducir a duración da batería
dgpu-applications = Aplicacións que están a usar a GPU dedicada { $gpu_name }

View file

@ -1,13 +1,13 @@
battery = Akkumulátor
battery-desc = Csökkentett energiafogyasztás és teljesítmény
battery-desc = Csökkentett energiafogyasztás és teljesítmény.
balanced = Kiegyensúlyozott
balanced-desc = Normál teljesítmény és akkumulátorhasználat
balanced-desc = Normál teljesítmény és akkumulátorhasználat.
performance = Nagy teljesítmény
performance-desc = Nagy teljesítmény és energiafogyasztás
max-charge = Az akkumulátor élettartamának növelése érdekében állítsa a maximális töltési szintet 80%-ra
seconds = másodperc
minutes = perc
hours = óra
performance-desc = Nagy teljesítmény és energiafogyasztás.
max-charge = Az akkumulátor élettartamának növelése érdekében állítsd a maximális töltési szintet 80%-ra
seconds = mp
minutes = p
hours = ó
until-empty = a lemerülésig
power-settings = Energia- és akkumulátorbeállítások…
dgpu-running = A dedikált GPU aktív, ami csökkentheti az akkumulátor élettartamát

View file

@ -1,14 +1,14 @@
battery = Risparmio energetico
battery-desc = Consumi minimi, performance ridotta.
balanced = Bilanciato
balanced-desc = Consumi bilanciati, performance dinamica.
performance = Performance
performance-desc = Consumi alti, performance massima.
max-charge = Migliora la durata di vita della batteria caricandola fino all'80%
battery = Batteria
battery-desc = Prestazioni e consumo energetico ridotti.
balanced = Equilibrato
balanced-desc = Prestazioni e consumo energetico normali.
performance = Prestazioni elevate
performance-desc = Prestazioni elevate e consumo energetico.
max-charge = Aumenta la durata della batteria impostando un valore massimo di carica dell’80%
seconds = s
minutes = m
hours = h
until-empty = fino alla scarica
until-empty = finché non è vuoto
power-settings = Impostazioni energetiche...
dgpu-running = La GPU discreta è attiva e può ridurre la durata della batteria
dgpu-applications = Applicazioni che stanno usando la GPU discreta { $gpu_name }

View file

@ -0,0 +1,14 @@
battery = Betarî
seconds = ç
minutes = x
hours = d
until-empty = heya vala dibe
power-settings = Sazkariyên hêz û betariyê...
battery-desc = Bikaranîna hêzê kêmkirî û performans.
balanced = Hevseng
balanced-desc = Performansa standard û bikaranîna betariyê.
performance = Performansa bilind
performance-desc = Performansa bilind û bikaranîna hêzê.
max-charge = Jiyana betariyê xwe dirêj bike bi riya danîna sînorê herî mezin yê tijîkrinê %80
dgpu-running = YKG a veqetandî çalak e û dikare jiyana betariyê kêm bike
dgpu-applications = Sepanên ku YKG a veqetandî ya { $gpu_name } bi kar tînin

View file

@ -1,14 +1,14 @@
battery = Bateria
battery = Economia de energia
battery-desc = Uso reduzido de energia e desempenho limitado.
balanced = Balanceado
balanced-desc = Desempenho padrão e uso de bateria moderado.
performance = Alto Desempenho
performance = Alto desempenho
performance-desc = Máximo desempenho e alto uso de energia.
max-charge = Aumente a vida útil da sua bateria definindo um valor máximo de carga de 80%
seconds = s
minutes = m
hours = h
until-empty = até esvaziar
until-empty = até ficar descarregada
power-settings = Configurações de energia e bateria...
dgpu-running = GPU discreta está ativa e pode reduzir a vida útil da bateria
dgpu-applications = Aplicativos usando GPU discreta { $gpu_name }
dgpu-running = A GPU dedicada está ativa e pode reduzir a vida útil da bateria
dgpu-applications = Aplicativos usando a GPU dedicada { $gpu_name }

View file

@ -0,0 +1,14 @@
power-settings = Подешавања напајања и батерије...
hours = ч
until-empty = до празне
battery-desc = Смањен учинак и употреба струје.
balanced = Уравнотежено
battery = Батерија
seconds = с
performance = Најбрже
performance-desc = Висок учинак и употреба струје.
minutes = м
balanced-desc = Уобичајени учинак и употреба батерије.
max-charge = Повећајте век трајања батерије подешавањем највише вредности пуњења на 80%
dgpu-running = Засебна графичка је покренута и може умањити век трајања батерије
dgpu-applications = Програми који користе засебну графичку { $gpu_name }

View file

@ -1,4 +1,4 @@
battery = 省電
battery = 電池
battery-desc = 降低效能與耗電量。
balanced = 平衡
balanced-desc = 標準效能與耗電量。
@ -6,9 +6,9 @@ performance = 高效能
performance-desc = 高效能與高耗電量。
max-charge = 將電池充電上限設定為 80%,以延長電池使用壽命
seconds = 秒
minutes = 分
hours = 時
minutes = 分鍾
hours = 小時
until-empty = 直到電量耗盡
power-settings = 電源與電池設定…
dgpu-running = 獨立顯示卡已啟用,可能會縮短電池續航力
dgpu-applications = 使用 { $gpu_name } 獨立顯示卡的應用程式
dgpu-applications = 使用獨立顯示卡 { $gpu_name } 的應用程式

View file

@ -40,7 +40,7 @@ use cosmic_settings_upower_subscription::{
};
use rustc_hash::FxHashMap;
use std::{path::PathBuf, sync::LazyLock, time::Duration};
use std::{path::PathBuf, time::Duration};
use tokio::sync::mpsc::UnboundedSender;
// XXX improve
@ -169,9 +169,24 @@ impl CosmicBatteryApplet {
self.charging_limit = Some(limit);
self.update_battery(self.battery_percent, self.on_battery);
}
fn panel_icon_name(&self) -> &str {
if self.no_battery {
if self.screen_brightness.is_some() {
self.display_icon_name.as_str()
} else if self.kbd_brightness.is_some() {
"keyboard-brightness-symbolic"
} else {
"plugged-into-power-symbolic"
}
} else {
self.icon_name.as_str()
}
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
enum Message {
TogglePopup,
CloseRequested(window::Id),
@ -351,23 +366,35 @@ impl cosmic::Application for CosmicBatteryApplet {
let _ = tx.send(KeyboardBacklightRequest::Get);
}
let new_id = window::Id::unique();
self.popup.replace(new_id);
let popup_settings = self.core.applet.get_popup_settings(
self.core.main_window_id().unwrap(),
new_id,
Some((1, 1)),
None,
None,
);
if let Some(tx) = self.power_profile_sender.as_ref() {
let _ = tx.send(PowerProfileRequest::Get);
}
if let Some(tx) = self.update_trigger.as_ref() {
let _ = tx.send(());
}
let mut tasks = vec![get_popup(popup_settings)];
let mut tasks = vec![cosmic::surface::surface_task(
cosmic::surface::action::app_popup(
|_| Default::default(),
|app: &mut Self| {
let new_id = window::Id::unique();
app.popup.replace(new_id);
let popup_settings = app.core.applet.get_popup_settings(
app.core.main_window_id().unwrap(),
new_id,
Some((1, 1)),
None,
None,
);
popup_settings
},
None,
),
)];
if let Some(tx) = &self.settings_daemon_sender {
let _ = tx.send(settings_daemon::Request::GetDisplayBrightness);
let _ = tx.send(settings_daemon::Request::GetMaxDisplayBrightness);
}
// Try again every time a popup is opened
if self.charging_limit.is_none() {
tasks.push(Task::perform(get_charging_limit(), |limit| {
@ -507,7 +534,7 @@ impl cosmic::Application for CosmicBatteryApplet {
Task::none()
}
fn view(&self) -> Element<Message> {
fn view(&self) -> Element<'_, Message> {
let is_horizontal = match self.core.applet.anchor {
PanelAnchor::Top | PanelAnchor::Bottom => true,
PanelAnchor::Left | PanelAnchor::Right => false,
@ -517,19 +544,9 @@ impl cosmic::Application for CosmicBatteryApplet {
let applet_padding = self.core.applet.suggested_padding(true);
let mut children = vec![
icon::from_name(if self.no_battery {
if self.screen_brightness.is_some() {
self.display_icon_name.as_str()
} else if self.kbd_brightness.is_some() {
"keyboard-brightness-symbolic"
} else {
self.icon_name.as_str()
}
} else {
self.icon_name.as_str()
})
.size(suggested_size.0)
.into(),
icon::from_name(self.panel_icon_name())
.size(suggested_size.0)
.into(),
];
if self.config.show_percentage && !self.no_battery {

View file

@ -1,6 +1,6 @@
[package]
name = "cosmic-applet-bluetooth"
version = "1.0.2"
version = "1.3.0"
edition = "2024"
license = "GPL-3.0-only"
@ -10,7 +10,7 @@ bluer = { version = "0.17", features = ["bluetoothd", "id"] }
futures.workspace = true
i18n-embed-fl.workspace = true
i18n-embed.workspace = true
libcosmic.workspace = true
cosmic.workspace = true
fastrand = "2.3.0"
rust-embed.workspace = true
rustc-hash.workspace = true

View file

@ -0,0 +1,12 @@
confirm-pin = Si us plau, verifiqueu que el següent PIN coincideix amb el mostrat a { $deviceName }
bluetooth = Bluetooth
unsuccessful = Error d'Emparellament
pairable = Emparellable
try-again = Reintentar
check-device = Si us plau, verifiqueu que { $deviceName } estigui encès, a l'abast i llest per emparellar-se.
other-devices = Altres dispositius Bluetooth
cancel = Cancel·lar
connected = Connectat
confirm = Confirmar
settings = Configuració del Bluetooth...
discoverable = Descobrible

View file

@ -0,0 +1,12 @@
cancel = Afbryd
confirm-pin = Bekræft venligst, at følgende pinkode matcher den, der vises på { $deviceName }
bluetooth = Bluetooth
unsuccessful = Parring Mislykkedes
pairable = Parbare
try-again = Prøv Igen
check-device = Sørg for, at { $deviceName } er slået til, inden for rækkevidde og klar til parring.
other-devices = Andre Bluetooth-enheder
connected = Forbundet
confirm = Bekræft
settings = Bluetooth-indstillinger...
discoverable = Synlig

View file

@ -1,6 +1,6 @@
bluetooth = Bluetooth
other-devices = Andere Bluetooth-Geräte
settings = Bluetooth-Einstellungen...
settings = Bluetooth-Einstellungen ...
connected = Verbunden
confirm-pin = Bitte bestätige, dass die folgende PIN mit der auf { $deviceName } angezeigten PIN übereinstimmt
confirm = Bestätigen

View file

@ -1,2 +1,12 @@
cancel = Ακύρωση
confirm = Επιβεβαίωση
bluetooth = Bluetooth
confirm-pin = Επιβεβαιώστε ότι το ακόλουθο PIN είναι ίδιο με αυτό που εμφανίζεται στο { $deviceName }
unsuccessful = Ανεπιτυχής σύζευξη
pairable = Συνδέσιμο
try-again = Δοκιμή ξανά
check-device = Βεβαιωθείτε ότι το { $deviceName } είναι ενεργοποιημένο, βρίσκεται εντός εμβέλειας και είναι έτοιμο για σύζευξη.
other-devices = Άλλες συσκευές Bluetooth
connected = Συνδέθηκε
settings = Ρυθμίσεις Bluetooth...
discoverable = Ορατό

View file

@ -0,0 +1,12 @@
cancel = Cancel
confirm-pin = Please confirm that the following PIN matches the one displayed on { $deviceName }
bluetooth = Bluetooth
unsuccessful = Pairing Unsuccessful
pairable = Pairable
try-again = Try Again
check-device = Make sure { $deviceName } is turned on, in range, and is ready to pair.
other-devices = Other Bluetooth devices
connected = Connected
confirm = Confirm
settings = Bluetooth settings...
discoverable = Discoverable

View file

@ -2,11 +2,11 @@ bluetooth = بلوتوث
other-devices = سایر دستگاه‌های بلوتوث
settings = تنظیمات بلوتوث...
connected = متصل
confirm-pin = لطفاً تأیید کنید که پین زیر با پین نمایش داده شده در {$deviceName} یکسان است.
confirm-pin = لطفاً تأیید کنید که پین زیر با پین نمایش داده شده در { $deviceName } یکسان است.
confirm = تأیید
cancel = لغو
unsuccessful = جفت‌سازی ناموفق بود
check-device = مطمئن شوید که {$deviceName} روشن، در محدوده و آماده جفت‌سازی است.
check-device = مطمئن شوید که { $deviceName } روشن، در محدوده و آماده جفت‌سازی است.
try-again = تلاش دوباره
discoverable = قابل شناسایی
pairable = قابل جفت‌سازی

View file

@ -0,0 +1,12 @@
cancel = Cancelar
bluetooth = Bluetooth
other-devices = Outros dispositivos Bluetooth
settings = Axustes Bluetooth…
connected = Conectado
confirm-pin = Por favor, confirme que o seguinte PIN coincide co que se mostra en{ $deviceName }
confirm = Confirmar
unsuccessful = Enparellamento non exitoso
check-device = Asegurate de que { $deviceName } está acendido, dentro do alcance e listo para emparellar.
try-again = Reintentar
discoverable = Atopable
pairable = Emparellable

View file

@ -2,11 +2,11 @@ bluetooth = ब्लूटूथ
other-devices = अन्य ब्लूटूथ उपकरण
settings = ब्लूटूथ सेटिंग्स...
connected = जुड़े हुए
confirm-pin = कृपया सुनिश्चित करें कि निम्नलिखित PIN {$deviceName} पर प्रदर्शित PIN से मेल खाता है
confirm-pin = कृपया सुनिश्चित करें कि निम्नलिखित PIN { $deviceName } पर प्रदर्शित PIN से मेल खाता है
confirm = पुष्टि करें
cancel = रद्द करें
unsuccessful = पेयरिंग असफल
check-device = सुनिश्चित करें कि {$deviceName} चालू है, रेंज में है, और पेयर करने के लिए तैयार है।
check-device = सुनिश्चित करें कि { $deviceName } चालू है, रेंज में है, और पेयर करने के लिए तैयार है।
try-again = फिर से प्रयास करें
discoverable = खोजने योग्य
pairable = पेयर करने योग्य

View file

@ -1,7 +1,7 @@
bluetooth = Bluetooth
other-devices = Egyéb Bluetooth-eszközök
settings = Bluetooth-beállítások…
connected = Csatlakoztatva
connected = Kapcsolódva
confirm-pin = Ellenőrizd, hogy a következő PIN-kód megegyezik-e a(z) { $deviceName } eszközön megjelenő PIN-kóddal
confirm = Megerősítés
cancel = Mégse

View file

@ -2,11 +2,11 @@ bluetooth = Bluetooth
other-devices = Altri dispositivi Bluetooth
settings = Impostazioni Bluetooth...
connected = Connesso
confirm-pin = Per favore conferma che il seguente PIN corrisponda a quello visualizzato nel dispositivo {$deviceName}
confirm-pin = Per favore conferma che il seguente PIN corrisponda a quello visualizzato nel dispositivo { $deviceName }
confirm = Conferma
cancel = Annulla
unsuccessful = Accoppiamento fallito
check-device = Assicurati che il dispositivo {$deviceName} sia acceso, a portata e pronto per l'accoppiamento.
check-device = Assicurati che il dispositivo { $deviceName } sia acceso, a portata e pronto per l'accoppiamento.
try-again = Riprova
discoverable = Visibile
pairable = Accoppiabile
pairable = Accoppiabile

View file

@ -0,0 +1,12 @@
cancel = Têk bibe
bluetooth = Bluetooth
connected = Hat girêdan
other-devices = Amûrên Bluetooth ên din
settings = Sazkariyên Bluetooth...
confirm-pin = Tika ye piştrast bike ku PIN a jêrîn bi ya ku li ser { $deviceName } tê xuyakirin re li hev tê
confirm = Bipejirîne
check-device = Piştrast bike ku { $deviceName } çalak e, di hundir rêjeyê de ye, û amade ye ji bo cotkirinê.
try-again = Dîsa hewl bide
discoverable = Dîtbar
pairable = Cotbar
unsuccessful = Cotkirin têkçûyî ye

View file

@ -0,0 +1 @@
cancel = ᱵᱟᱹᱛᱤᱞ

Some files were not shown because too many files have changed in this diff Show more