Compare commits
22 commits
ebecfa6b7d
...
4636d93eba
| Author | SHA1 | Date | |
|---|---|---|---|
| 4636d93eba | |||
| 7df503977d | |||
| 43415d98b1 | |||
| c38dba6a52 | |||
|
|
090503ab2b | ||
|
|
658c38e913 | ||
|
|
3f530fd31f | ||
|
|
9c4299429e | ||
| da53a9f45f | |||
| 339ac4e3e4 | |||
| cc501c7637 | |||
| 93ab0f391d | |||
| 4416a5e9ea | |||
| 6c7f31e1ae | |||
| f2f53fc4d2 | |||
| 82e00a3e16 | |||
|
|
bcc8072a3b | ||
|
|
57a435e830 | ||
|
|
6fe087f4fd | ||
| 0fa93ba21f | |||
| 8fc11581ad | |||
| d090e60370 |
49 changed files with 587 additions and 1265 deletions
544
Cargo.lock
generated
544
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -61,7 +61,6 @@ tokio = { version = "1.49.0", features = ["full"] }
|
||||||
# cosmic-config = { path = "../libcosmic/cosmic-config" }
|
# cosmic-config = { path = "../libcosmic/cosmic-config" }
|
||||||
cosmic-config = { path = "../libcosmic/cosmic-config" }
|
cosmic-config = { path = "../libcosmic/cosmic-config" }
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
zlink = "0.5.0"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-app-list"
|
name = "cosmic-app-list"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-app-list-config"
|
name = "cosmic-app-list-config"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-applet-a11y"
|
name = "cosmic-applet-a11y"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,5 @@ screen-reader = スクリーンリーダー
|
||||||
magnifier = 拡大鏡
|
magnifier = 拡大鏡
|
||||||
settings = アクセシビリティ設定…
|
settings = アクセシビリティ設定…
|
||||||
invert-colors = 色を反転
|
invert-colors = 色を反転
|
||||||
filter-colors = カラーフィルター
|
filter-colors = 色のフィルター
|
||||||
high-contrast = ハイコントラスト
|
high-contrast = ハイコントラスト
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-applet-audio"
|
name = "cosmic-applet-audio"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures.workspace = true
|
|
||||||
i18n-embed-fl.workspace = true
|
i18n-embed-fl.workspace = true
|
||||||
i18n-embed.workspace = true
|
i18n-embed.workspace = true
|
||||||
cosmic.workspace = true
|
cosmic.workspace = true
|
||||||
|
|
@ -19,9 +18,6 @@ tracing.workspace = true
|
||||||
url = "2"
|
url = "2"
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
zbus.workspace = true
|
zbus.workspace = true
|
||||||
zlink.workspace = true
|
|
||||||
intmap = "3.1.3"
|
|
||||||
|
|
||||||
[dependencies.cosmic-settings-audio-client]
|
[dependencies.cosmic-settings-sound-subscription]
|
||||||
path = "../../cosmic-settings-daemon/audio-client"
|
path = "../../cosmic-settings/subscriptions/sound"
|
||||||
features = ["codec"]
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -2,13 +2,12 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
mod localize;
|
mod localize;
|
||||||
mod model;
|
|
||||||
mod mouse_area;
|
mod mouse_area;
|
||||||
|
|
||||||
use crate::localize::localize;
|
use crate::localize::localize;
|
||||||
use config::{AudioAppletConfig, amplification_sink, amplification_source};
|
use config::{AudioAppletConfig, amplification_sink, amplification_source};
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
Apply, Element, Renderer, Task, Theme, app,
|
Element, Renderer, Task, Theme, app,
|
||||||
applet::{
|
applet::{
|
||||||
column as applet_column,
|
column as applet_column,
|
||||||
cosmic_panel_config::PanelAnchor,
|
cosmic_panel_config::PanelAnchor,
|
||||||
|
|
@ -19,24 +18,18 @@ use cosmic::{
|
||||||
cosmic_config::CosmicConfigEntry,
|
cosmic_config::CosmicConfigEntry,
|
||||||
cosmic_theme::Spacing,
|
cosmic_theme::Spacing,
|
||||||
iced::{
|
iced::{
|
||||||
self, Alignment, Length, Rectangle, Subscription,
|
self, Alignment, Length, Subscription,
|
||||||
futures::StreamExt,
|
futures::StreamExt,
|
||||||
widget::{self, column, row, slider},
|
widget::{self, column, row, slider},
|
||||||
window,
|
window,
|
||||||
},
|
},
|
||||||
surface, theme,
|
surface, theme,
|
||||||
widget::{
|
widget::{Row, button, container, divider, icon, space, text, toggler},
|
||||||
Row, button, container, divider, icon,
|
|
||||||
rectangle_tracker::{RectangleTracker, RectangleUpdate, rectangle_tracker_subscription},
|
|
||||||
space, text, toggler,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use cosmic_settings_audio_client::{self as audio_client, CosmicAudioProxy};
|
use cosmic_settings_sound_subscription as css;
|
||||||
use futures::SinkExt;
|
|
||||||
use iced::platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup};
|
use iced::platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup};
|
||||||
use mpris_subscription::{MprisRequest, MprisUpdate};
|
use mpris_subscription::{MprisRequest, MprisUpdate};
|
||||||
use mpris2_zbus::player::PlaybackStatus;
|
use mpris2_zbus::player::PlaybackStatus;
|
||||||
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod mpris_subscription;
|
mod mpris_subscription;
|
||||||
|
|
@ -57,10 +50,8 @@ pub struct Audio {
|
||||||
core: cosmic::app::Core,
|
core: cosmic::app::Core,
|
||||||
/// Track the applet's popup window.
|
/// Track the applet's popup window.
|
||||||
popup: Option<window::Id>,
|
popup: Option<window::Id>,
|
||||||
/// Varlink connection to `com.system76.CosmicSettings.Audio`.
|
/// The model from cosmic-settings for managing pipewire devices.
|
||||||
audio_client: Option<Rc<RefCell<audio_client::Client>>>,
|
model: css::Model,
|
||||||
/// Known audio device state
|
|
||||||
model: model::Model,
|
|
||||||
/// Whether to expand the revealer of a source or sink device.
|
/// Whether to expand the revealer of a source or sink device.
|
||||||
is_open: IsOpen,
|
is_open: IsOpen,
|
||||||
/// Max slider volume for the sink device, as determined by the amplification property.
|
/// Max slider volume for the sink device, as determined by the amplification property.
|
||||||
|
|
@ -77,14 +68,12 @@ pub struct Audio {
|
||||||
player_status: Option<mpris_subscription::PlayerStatus>,
|
player_status: Option<mpris_subscription::PlayerStatus>,
|
||||||
/// Used to request an activation token for opening cosmic-settings.
|
/// Used to request an activation token for opening cosmic-settings.
|
||||||
token_tx: Option<calloop::channel::Sender<TokenRequest>>,
|
token_tx: Option<calloop::channel::Sender<TokenRequest>>,
|
||||||
rectangle_tracker: Option<RectangleTracker<u32>>,
|
|
||||||
rectangle: Option<iced::Rectangle>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Audio {
|
impl Audio {
|
||||||
fn output_icon_name(&self) -> &'static str {
|
fn output_icon_name(&self) -> &'static str {
|
||||||
let volume = self.model.active_sink.volume;
|
let volume = self.model.sink_volume;
|
||||||
let mute = self.model.active_sink.mute;
|
let mute = self.model.sink_mute;
|
||||||
if mute || volume == 0 {
|
if mute || volume == 0 {
|
||||||
"audio-volume-muted-symbolic"
|
"audio-volume-muted-symbolic"
|
||||||
} else if volume < 33 {
|
} else if volume < 33 {
|
||||||
|
|
@ -99,8 +88,8 @@ impl Audio {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input_icon_name(&self) -> &'static str {
|
fn input_icon_name(&self) -> &'static str {
|
||||||
let volume = self.model.active_source.volume;
|
let volume = self.model.source_volume;
|
||||||
let mute = self.model.active_source.mute;
|
let mute = self.model.source_mute;
|
||||||
if mute || volume == 0 {
|
if mute || volume == 0 {
|
||||||
"microphone-sensitivity-muted-symbolic"
|
"microphone-sensitivity-muted-symbolic"
|
||||||
} else if volume < 33 {
|
} else if volume < 33 {
|
||||||
|
|
@ -121,10 +110,8 @@ enum IsOpen {
|
||||||
Input,
|
Input,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
/// Connection to `com.system76.CosmicSettings`.
|
|
||||||
Client(Arc<audio_client::Client>),
|
|
||||||
Ignore,
|
Ignore,
|
||||||
SetSinkVolume(u32),
|
SetSinkVolume(u32),
|
||||||
SetSourceVolume(u32),
|
SetSourceVolume(u32),
|
||||||
|
|
@ -142,9 +129,8 @@ pub enum Message {
|
||||||
MprisRequest(MprisRequest),
|
MprisRequest(MprisRequest),
|
||||||
Token(TokenUpdate),
|
Token(TokenUpdate),
|
||||||
OpenSettings,
|
OpenSettings,
|
||||||
Subscription(audio_client::Event),
|
Subscription(css::Message),
|
||||||
Surface(surface::Action),
|
Surface(surface::Action),
|
||||||
Rectangle(RectangleUpdate<u32>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
|
@ -256,9 +242,15 @@ impl cosmic::Application for Audio {
|
||||||
const APP_ID: &'static str = "com.system76.CosmicAppletAudio";
|
const APP_ID: &'static str = "com.system76.CosmicAppletAudio";
|
||||||
|
|
||||||
fn init(core: cosmic::app::Core, _flags: ()) -> (Self, app::Task<Message>) {
|
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();
|
||||||
|
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
core,
|
core,
|
||||||
|
model,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Task::none(),
|
Task::none(),
|
||||||
|
|
@ -279,14 +271,6 @@ impl cosmic::Application for Audio {
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> app::Task<Message> {
|
fn update(&mut self, message: Message) -> app::Task<Message> {
|
||||||
match 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::Ignore => {}
|
Message::Ignore => {}
|
||||||
Message::TogglePopup => {
|
Message::TogglePopup => {
|
||||||
if let Some(p) = self.popup.take() {
|
if let Some(p) = self.popup.take() {
|
||||||
|
|
@ -307,21 +291,14 @@ impl cosmic::Application for Audio {
|
||||||
(100, &[][..])
|
(100, &[][..])
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut popup_settings = self.core.applet.get_popup_settings(
|
let popup_settings = self.core.applet.get_popup_settings(
|
||||||
self.core.main_window_id().unwrap(),
|
self.core.main_window_id().unwrap(),
|
||||||
new_id,
|
new_id,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
if let Some(r) = self.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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return get_popup(popup_settings);
|
return get_popup(popup_settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -341,76 +318,54 @@ impl cosmic::Application for Audio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::Subscription(message) => {
|
Message::Subscription(message) => {
|
||||||
self.model.update(message);
|
return self
|
||||||
|
.model
|
||||||
|
.update(message)
|
||||||
|
.map(|message| Message::Subscription(message).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::SetDefaultSink(pos) => {
|
Message::SetDefaultSink(pos) => {
|
||||||
if let Some(&pos) = self.model.sinks.sorted_index.get(pos)
|
return self
|
||||||
&& let Some(&node_id) = self.model.sinks.id.get(pos as usize)
|
.model
|
||||||
&& let Some(client) = self.audio_client.as_mut()
|
.set_default_sink(pos)
|
||||||
{
|
.map(|message| Message::Subscription(message).into());
|
||||||
futures::executor::block_on(async {
|
|
||||||
_ = client.borrow_mut().conn.set_default(node_id, true).await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::SetDefaultSource(pos) => {
|
Message::SetDefaultSource(pos) => {
|
||||||
if let Some(&pos) = self.model.sources.sorted_index.get(pos)
|
return self
|
||||||
&& let Some(&node_id) = self.model.sources.id.get(pos as usize)
|
.model
|
||||||
&& let Some(client) = self.audio_client.as_mut()
|
.set_default_source(pos)
|
||||||
{
|
.map(|message| Message::Subscription(message).into());
|
||||||
futures::executor::block_on(async {
|
|
||||||
_ = client.borrow_mut().conn.set_default(node_id, true).await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::ToggleSinkMute => {
|
Message::ToggleSinkMute => self.model.toggle_sink_mute(),
|
||||||
if let Some(ref mut client) = self.audio_client {
|
|
||||||
futures::executor::block_on(async {
|
|
||||||
_ = client.borrow_mut().conn.sink_mute_toggle().await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Message::ToggleSourceMute => {
|
Message::ToggleSourceMute => self.model.toggle_source_mute(),
|
||||||
if let Some(ref mut client) = self.audio_client {
|
|
||||||
futures::executor::block_on(async {
|
|
||||||
_ = client.borrow_mut().conn.source_mute_toggle().await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Message::SetSinkVolume(volume) => {
|
Message::SetSinkVolume(volume) => {
|
||||||
if let Some(ref mut client) = self.audio_client {
|
return self
|
||||||
self.model.active_sink.volume = volume;
|
.model
|
||||||
self.model.active_sink.volume_text = volume.to_string();
|
.set_sink_volume(volume)
|
||||||
futures::executor::block_on(async {
|
.map(|message| Message::Subscription(message).into());
|
||||||
_ = client.borrow_mut().conn.set_sink_volume(volume).await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::SetSourceVolume(volume) => {
|
Message::SetSourceVolume(volume) => {
|
||||||
if let Some(ref mut client) = self.audio_client {
|
return self
|
||||||
self.model.active_source.volume = volume;
|
.model
|
||||||
self.model.active_source.volume_text = volume.to_string();
|
.set_source_volume(volume)
|
||||||
futures::executor::block_on(async {
|
.map(|message| Message::Subscription(message).into());
|
||||||
_ = client.borrow_mut().conn.set_source_volume(volume).await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::ToggleMediaControlsInTopPanel(enabled) => {
|
Message::ToggleMediaControlsInTopPanel(enabled) => {
|
||||||
self.config.show_media_controls_in_top_panel = enabled;
|
self.config.show_media_controls_in_top_panel = enabled;
|
||||||
if let Ok(helper) =
|
if let Ok(helper) =
|
||||||
cosmic::cosmic_config::Config::new(Self::APP_ID, AudioAppletConfig::VERSION)
|
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) => {
|
Message::CloseRequested(id) => {
|
||||||
if Some(id) == self.popup {
|
if Some(id) == self.popup {
|
||||||
self.popup = None;
|
self.popup = None;
|
||||||
|
|
@ -506,12 +461,6 @@ impl cosmic::Application for Audio {
|
||||||
cosmic::app::Action::Surface(a),
|
cosmic::app::Action::Surface(a),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Message::Client(client) => {
|
|
||||||
if let Some(client) = Arc::into_inner(client) {
|
|
||||||
self.audio_client = Some(Rc::new(RefCell::new(client)));
|
|
||||||
self.model = model::Model::default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
|
|
@ -527,50 +476,7 @@ impl cosmic::Application for Audio {
|
||||||
}),
|
}),
|
||||||
mpris_subscription::mpris_subscription(0).map(Message::Mpris),
|
mpris_subscription::mpris_subscription(0).map(Message::Mpris),
|
||||||
activation_token_subscription(0).map(Message::Token),
|
activation_token_subscription(0).map(Message::Token),
|
||||||
Subscription::run(|| {
|
Subscription::run(|| css::watch().map(Message::Subscription)),
|
||||||
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)),
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -611,23 +517,24 @@ impl cosmic::Application for Audio {
|
||||||
return Message::Ignore;
|
return Message::Ignore;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_volume = (self.model.active_sink.volume as f64 + (scroll_vector as f64))
|
let new_volume = (self.model.sink_volume as f64 + (scroll_vector as f64))
|
||||||
.clamp(0.0, self.max_sink_volume as f64);
|
.clamp(0.0, self.max_sink_volume as f64);
|
||||||
Message::SetSinkVolume(new_volume.round() 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| {
|
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
|
// if we have a configure for width and height, we're in a overflow popup
|
||||||
c.width > 0. && c.height > 0.
|
c.width > 0. && c.height > 0.
|
||||||
}))
|
}))
|
||||||
.then(|| self.playback_buttons());
|
.then(|| self.playback_buttons());
|
||||||
|
|
||||||
let mut ret = if let Some(playback_buttons) = playback_buttons
|
self.core
|
||||||
|
.applet
|
||||||
|
.autosize_window(
|
||||||
|
if let Some(playback_buttons) = playback_buttons
|
||||||
&& !playback_buttons.is_empty()
|
&& !playback_buttons.is_empty()
|
||||||
{
|
{
|
||||||
has_playback_buttons = true;
|
match self.core.applet.anchor {
|
||||||
Element::from(match self.core.applet.anchor {
|
|
||||||
PanelAnchor::Left | PanelAnchor::Right => Element::from(
|
PanelAnchor::Left | PanelAnchor::Right => Element::from(
|
||||||
applet_column::Column::with_children(playback_buttons)
|
applet_column::Column::with_children(playback_buttons)
|
||||||
.push(btn)
|
.push(btn)
|
||||||
|
|
@ -651,24 +558,12 @@ impl cosmic::Application for Audio {
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
} else {
|
} else {
|
||||||
btn.into()
|
btn.into()
|
||||||
};
|
},
|
||||||
|
)
|
||||||
if let Some(tracker) = self.rectangle_tracker.as_ref()
|
.into()
|
||||||
&& 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> {
|
fn view_window(&self, _id: window::Id) -> Element<'_, Message> {
|
||||||
|
|
@ -676,21 +571,19 @@ impl cosmic::Application for Audio {
|
||||||
space_xxs, space_s, ..
|
space_xxs, space_s, ..
|
||||||
} = theme::active().cosmic().spacing;
|
} = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
let sink: Option<&str> = self
|
let sink = self
|
||||||
.model
|
.model
|
||||||
.sinks
|
.active_sink()
|
||||||
.active()
|
.and_then(|pos| self.model.sinks().get(pos));
|
||||||
.map(|pos| self.model.sinks.sorted_display[pos].as_ref());
|
let source = self
|
||||||
let source: Option<&str> = self
|
|
||||||
.model
|
.model
|
||||||
.sources
|
.active_source()
|
||||||
.active()
|
.and_then(|pos| self.model.sources().get(pos));
|
||||||
.map(|pos| self.model.sources.sorted_display[pos].as_ref());
|
|
||||||
|
|
||||||
let mut audio_content = {
|
let mut audio_content = {
|
||||||
let output_slider = slider(
|
let output_slider = slider(
|
||||||
0..=self.max_sink_volume,
|
0..=self.max_sink_volume,
|
||||||
self.model.active_sink.volume,
|
self.model.sink_volume,
|
||||||
Message::SetSinkVolume,
|
Message::SetSinkVolume,
|
||||||
)
|
)
|
||||||
.width(Length::FillPortion(5))
|
.width(Length::FillPortion(5))
|
||||||
|
|
@ -698,7 +591,7 @@ impl cosmic::Application for Audio {
|
||||||
|
|
||||||
let input_slider = slider(
|
let input_slider = slider(
|
||||||
0..=self.max_source_volume,
|
0..=self.max_source_volume,
|
||||||
self.model.active_source.volume,
|
self.model.source_volume,
|
||||||
Message::SetSourceVolume,
|
Message::SetSourceVolume,
|
||||||
)
|
)
|
||||||
.width(Length::FillPortion(5))
|
.width(Length::FillPortion(5))
|
||||||
|
|
@ -717,7 +610,7 @@ impl cosmic::Application for Audio {
|
||||||
.line_height(24)
|
.line_height(24)
|
||||||
.on_press(Message::ToggleSinkMute),
|
.on_press(Message::ToggleSinkMute),
|
||||||
output_slider,
|
output_slider,
|
||||||
container(text(&self.model.active_sink.volume_text).size(16))
|
container(text(&self.model.sink_volume_text).size(16))
|
||||||
.width(Length::FillPortion(1))
|
.width(Length::FillPortion(1))
|
||||||
.align_x(Alignment::End)
|
.align_x(Alignment::End)
|
||||||
]
|
]
|
||||||
|
|
@ -736,7 +629,7 @@ impl cosmic::Application for Audio {
|
||||||
.line_height(24)
|
.line_height(24)
|
||||||
.on_press(Message::ToggleSourceMute),
|
.on_press(Message::ToggleSourceMute),
|
||||||
input_slider,
|
input_slider,
|
||||||
container(text(&self.model.active_source.volume_text).size(16))
|
container(text(&self.model.source_volume_text).size(16))
|
||||||
.width(Length::FillPortion(1))
|
.width(Length::FillPortion(1))
|
||||||
.align_x(Alignment::End)
|
.align_x(Alignment::End)
|
||||||
]
|
]
|
||||||
|
|
@ -751,7 +644,7 @@ impl cosmic::Application for Audio {
|
||||||
Some(sink) => sink.to_owned(),
|
Some(sink) => sink.to_owned(),
|
||||||
None => fl!("no-device"),
|
None => fl!("no-device"),
|
||||||
},
|
},
|
||||||
&self.model.sinks.sorted_display,
|
self.model.sinks(),
|
||||||
Message::OutputToggle,
|
Message::OutputToggle,
|
||||||
Message::SetDefaultSink,
|
Message::SetDefaultSink,
|
||||||
),
|
),
|
||||||
|
|
@ -762,7 +655,7 @@ impl cosmic::Application for Audio {
|
||||||
Some(source) => source.to_owned(),
|
Some(source) => source.to_owned(),
|
||||||
None => fl!("no-device"),
|
None => fl!("no-device"),
|
||||||
},
|
},
|
||||||
&self.model.sources.sorted_display,
|
self.model.sources(),
|
||||||
Message::InputToggle,
|
Message::InputToggle,
|
||||||
Message::SetDefaultSource,
|
Message::SetDefaultSource,
|
||||||
)
|
)
|
||||||
|
|
@ -818,7 +711,11 @@ impl cosmic::Application for Audio {
|
||||||
}
|
}
|
||||||
if let Some(play) = self.is_play() {
|
if let Some(play) = self.is_play() {
|
||||||
control_elements.push(
|
control_elements.push(
|
||||||
button::icon(icon::from_name(if play { PLAY } else { PAUSE }).symbolic(true))
|
button::icon(
|
||||||
|
icon::from_name(if play { PLAY } else { PAUSE })
|
||||||
|
.size(32)
|
||||||
|
.symbolic(true),
|
||||||
|
)
|
||||||
.extra_small()
|
.extra_small()
|
||||||
.class(cosmic::theme::Button::AppletIcon)
|
.class(cosmic::theme::Button::AppletIcon)
|
||||||
.on_press(if play {
|
.on_press(if play {
|
||||||
|
|
@ -877,21 +774,20 @@ impl cosmic::Application for Audio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn revealer<'a>(
|
fn revealer(
|
||||||
open: bool,
|
open: bool,
|
||||||
title: String,
|
title: String,
|
||||||
selected: String,
|
selected: String,
|
||||||
devices: &'a [Arc<str>],
|
devices: &[String],
|
||||||
toggle: Message,
|
toggle: Message,
|
||||||
mut change: impl FnMut(usize) -> Message + 'static,
|
mut change: impl FnMut(usize) -> Message + 'static,
|
||||||
) -> widget::Column<'a, Message, crate::Theme, Renderer> {
|
) -> widget::Column<'static, Message, crate::Theme, Renderer> {
|
||||||
if open {
|
if open {
|
||||||
devices.iter().enumerate().fold(
|
devices.iter().cloned().enumerate().fold(
|
||||||
column![revealer_head(open, title, selected, toggle)].width(Length::Fill),
|
column![revealer_head(open, title, selected, toggle)].width(Length::Fill),
|
||||||
move |col, (id, name)| {
|
|col, (id, name)| {
|
||||||
col.push(
|
col.push(
|
||||||
text::body(name.as_ref())
|
menu_button(text::body(name))
|
||||||
.apply(menu_button)
|
|
||||||
.on_press(change(id))
|
.on_press(change(id))
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.padding([8, 48]),
|
.padding([8, 48]),
|
||||||
|
|
@ -909,9 +805,9 @@ fn revealer_head(
|
||||||
selected: String,
|
selected: String,
|
||||||
toggle: Message,
|
toggle: Message,
|
||||||
) -> cosmic::widget::Button<'static, Message> {
|
) -> cosmic::widget::Button<'static, Message> {
|
||||||
cosmic::widget::column::with_capacity(2)
|
menu_button(column![
|
||||||
.push(text::body(title).width(Length::Fill))
|
text::body(title).width(Length::Fill),
|
||||||
.push(text::caption(selected))
|
text::caption(selected),
|
||||||
.apply(menu_button)
|
])
|
||||||
.on_press(toggle)
|
.on_press(toggle)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,393 +0,0 @@
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-applet-battery"
|
name = "cosmic-applet-battery"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -169,20 +169,6 @@ impl CosmicBatteryApplet {
|
||||||
self.charging_limit = Some(limit);
|
self.charging_limit = Some(limit);
|
||||||
self.update_battery(self.battery_percent, self.on_battery);
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -536,7 +522,17 @@ impl cosmic::Application for CosmicBatteryApplet {
|
||||||
let applet_padding = self.core.applet.suggested_padding(true);
|
let applet_padding = self.core.applet.suggested_padding(true);
|
||||||
|
|
||||||
let mut children = vec![
|
let mut children = vec![
|
||||||
icon::from_name(self.panel_icon_name())
|
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)
|
.size(suggested_size.0)
|
||||||
.into(),
|
.into(),
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-applet-bluetooth"
|
name = "cosmic-applet-bluetooth"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -21,7 +21,7 @@ use cosmic::{
|
||||||
widget::{Column, column, container, row},
|
widget::{Column, column, container, row},
|
||||||
},
|
},
|
||||||
theme,
|
theme,
|
||||||
widget::{button, divider, icon, indeterminate_circular, scrollable, text},
|
widget::{button, divider, icon, scrollable, text},
|
||||||
};
|
};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use std::{collections::HashMap, time::Duration};
|
use std::{collections::HashMap, time::Duration};
|
||||||
|
|
@ -368,12 +368,14 @@ impl cosmic::Application for CosmicBluetoothApplet {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
BluerDeviceStatus::Paired => {}
|
BluerDeviceStatus::Paired => {}
|
||||||
BluerDeviceStatus::Connecting
|
BluerDeviceStatus::Connecting | BluerDeviceStatus::Disconnecting => {
|
||||||
| BluerDeviceStatus::Disconnecting
|
row = row.push(
|
||||||
| BluerDeviceStatus::Pairing => {
|
icon::from_name("process-working-symbolic")
|
||||||
row = row.push(indeterminate_circular().size(24.0));
|
.size(24)
|
||||||
|
.symbolic(true),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
BluerDeviceStatus::Disconnected => continue,
|
BluerDeviceStatus::Disconnected | BluerDeviceStatus::Pairing => continue,
|
||||||
}
|
}
|
||||||
|
|
||||||
known_bluetooth.push(
|
known_bluetooth.push(
|
||||||
|
|
|
||||||
|
|
@ -567,22 +567,23 @@ impl BluerSessionState {
|
||||||
tick(&mut interval).await;
|
tick(&mut interval).await;
|
||||||
let new_status = adapter_clone.is_powered().await.unwrap_or_default();
|
let new_status = adapter_clone.is_powered().await.unwrap_or_default();
|
||||||
devices = build_device_list(devices, &adapter_clone).await;
|
devices = build_device_list(devices, &adapter_clone).await;
|
||||||
let power_changed = new_status != status;
|
if new_status != status {
|
||||||
status = new_status;
|
status = new_status;
|
||||||
let state = BluerState {
|
let state = BluerState {
|
||||||
devices: devices.clone(),
|
devices: devices.clone(),
|
||||||
bluetooth_enabled: status,
|
bluetooth_enabled: status,
|
||||||
};
|
};
|
||||||
if power_changed && state.bluetooth_enabled {
|
if state.bluetooth_enabled {
|
||||||
for d in &state.devices {
|
for d in &state.devices {
|
||||||
if d.paired_and_trusted() {
|
if d.paired_and_trusted() {
|
||||||
_ = req_tx.send(BluerRequest::ConnectDevice(d.address)).await;
|
_ = req_tx.send(BluerRequest::ConnectDevice(d.address)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = wake_up_discover_tx.send(()).await;
|
|
||||||
}
|
}
|
||||||
|
_ = wake_up_discover_tx.send(()).await;
|
||||||
let _ = tx.send(BluerSessionEvent::ChangesProcessed(state)).await;
|
let _ = tx.send(BluerSessionEvent::ChangesProcessed(state)).await;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -672,9 +673,6 @@ impl BluerSessionState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always refresh power state from the adapter
|
|
||||||
// to avoid stale cached is_powered values
|
|
||||||
is_powered = adapter_clone.is_powered().await.unwrap_or_default();
|
|
||||||
let _ = tx
|
let _ = tx
|
||||||
.send(BluerSessionEvent::ChangesProcessed(BluerState {
|
.send(BluerSessionEvent::ChangesProcessed(BluerState {
|
||||||
devices: devices.clone(),
|
devices: devices.clone(),
|
||||||
|
|
@ -854,7 +852,13 @@ async fn bluer_state(adapter: &Adapter) -> BluerState {
|
||||||
|
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
async fn build_device_list(mut devices: Vec<BluerDevice>, adapter: &Adapter) -> Vec<BluerDevice> {
|
async fn build_device_list(mut devices: Vec<BluerDevice>, adapter: &Adapter) -> Vec<BluerDevice> {
|
||||||
let addrs = adapter.device_addresses().await.unwrap_or_default();
|
let addrs: Vec<Address> = adapter
|
||||||
|
.device_addresses()
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|addr| !devices.iter().any(|d| d.address == *addr))
|
||||||
|
.collect();
|
||||||
|
|
||||||
devices.clear();
|
devices.clear();
|
||||||
if addrs.len() > devices.capacity() {
|
if addrs.len() > devices.capacity() {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-applet-input-sources"
|
name = "cosmic-applet-input-sources"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
show-keyboard-layout = Awaya kilîtdankê nîşan bide...
|
|
||||||
keyboard-settings = Sazkariyên kilîtdankê...
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-applet-minimize"
|
name = "cosmic-applet-minimize"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,6 @@ struct AppData {
|
||||||
toplevel_info_state: ToplevelInfoState,
|
toplevel_info_state: ToplevelInfoState,
|
||||||
toplevel_manager_state: ToplevelManagerState,
|
toplevel_manager_state: ToplevelManagerState,
|
||||||
seat_state: SeatState,
|
seat_state: SeatState,
|
||||||
captured_toplevels: std::collections::HashSet<ExtForeignToplevelHandleV1>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CaptureData {
|
struct CaptureData {
|
||||||
|
|
@ -352,36 +351,6 @@ impl AppData {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_toplevel(&mut self, toplevel: &ExtForeignToplevelHandleV1, is_new: bool) {
|
|
||||||
let Some(info) = self.toplevel_info_state.info(toplevel) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if info
|
|
||||||
.state
|
|
||||||
.contains(&zcosmic_toplevel_handle_v1::State::Minimized)
|
|
||||||
{
|
|
||||||
// Capture a thumbnail once, on the transition into minimized.
|
|
||||||
if self.captured_toplevels.insert(toplevel.clone()) {
|
|
||||||
self.send_image(toplevel.clone());
|
|
||||||
}
|
|
||||||
let update = if is_new {
|
|
||||||
ToplevelUpdate::Add(info.clone())
|
|
||||||
} else {
|
|
||||||
ToplevelUpdate::Update(info.clone())
|
|
||||||
};
|
|
||||||
let _ = futures::executor::block_on(self.tx.send(WaylandUpdate::Toplevel(update)));
|
|
||||||
} else {
|
|
||||||
self.remove_toplevel(toplevel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_toplevel(&mut self, toplevel: &ExtForeignToplevelHandleV1) {
|
|
||||||
self.captured_toplevels.remove(toplevel);
|
|
||||||
let _ = futures::executor::block_on(self.tx.send(WaylandUpdate::Toplevel(
|
|
||||||
ToplevelUpdate::Remove(toplevel.clone()),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelInfoHandler for AppData {
|
impl ToplevelInfoHandler for AppData {
|
||||||
|
|
@ -395,7 +364,23 @@ impl ToplevelInfoHandler for AppData {
|
||||||
_qh: &QueueHandle<Self>,
|
_qh: &QueueHandle<Self>,
|
||||||
toplevel: &ExtForeignToplevelHandleV1,
|
toplevel: &ExtForeignToplevelHandleV1,
|
||||||
) {
|
) {
|
||||||
self.handle_toplevel(toplevel, true);
|
if let Some(info) = self.toplevel_info_state.info(toplevel) {
|
||||||
|
if info
|
||||||
|
.state
|
||||||
|
.contains(&zcosmic_toplevel_handle_v1::State::Minimized)
|
||||||
|
{
|
||||||
|
// spawn thread for sending the image
|
||||||
|
self.send_image(toplevel.clone());
|
||||||
|
let _ = futures::executor::block_on(
|
||||||
|
self.tx
|
||||||
|
.send(WaylandUpdate::Toplevel(ToplevelUpdate::Add(info.clone()))),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let _ = futures::executor::block_on(self.tx.send(WaylandUpdate::Toplevel(
|
||||||
|
ToplevelUpdate::Remove(toplevel.clone()),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_toplevel(
|
fn update_toplevel(
|
||||||
|
|
@ -404,7 +389,21 @@ impl ToplevelInfoHandler for AppData {
|
||||||
_qh: &QueueHandle<Self>,
|
_qh: &QueueHandle<Self>,
|
||||||
toplevel: &ExtForeignToplevelHandleV1,
|
toplevel: &ExtForeignToplevelHandleV1,
|
||||||
) {
|
) {
|
||||||
self.handle_toplevel(toplevel, false);
|
if let Some(info) = self.toplevel_info_state.info(toplevel) {
|
||||||
|
if info
|
||||||
|
.state
|
||||||
|
.contains(&zcosmic_toplevel_handle_v1::State::Minimized)
|
||||||
|
{
|
||||||
|
self.send_image(toplevel.clone());
|
||||||
|
let _ = futures::executor::block_on(self.tx.send(WaylandUpdate::Toplevel(
|
||||||
|
ToplevelUpdate::Update(info.clone()),
|
||||||
|
)));
|
||||||
|
} else {
|
||||||
|
let _ = futures::executor::block_on(self.tx.send(WaylandUpdate::Toplevel(
|
||||||
|
ToplevelUpdate::Remove(toplevel.clone()),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toplevel_closed(
|
fn toplevel_closed(
|
||||||
|
|
@ -413,7 +412,9 @@ impl ToplevelInfoHandler for AppData {
|
||||||
_qh: &QueueHandle<Self>,
|
_qh: &QueueHandle<Self>,
|
||||||
toplevel: &ExtForeignToplevelHandleV1,
|
toplevel: &ExtForeignToplevelHandleV1,
|
||||||
) {
|
) {
|
||||||
self.remove_toplevel(toplevel);
|
let _ = futures::executor::block_on(self.tx.send(WaylandUpdate::Toplevel(
|
||||||
|
ToplevelUpdate::Remove(toplevel.clone()),
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -481,7 +482,6 @@ pub(crate) fn wayland_handler(
|
||||||
toplevel_manager_state: ToplevelManagerState::new(®istry_state, &qh),
|
toplevel_manager_state: ToplevelManagerState::new(®istry_state, &qh),
|
||||||
queue_handle: qh,
|
queue_handle: qh,
|
||||||
registry_state,
|
registry_state,
|
||||||
captured_toplevels: std::collections::HashSet::new(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-applet-network"
|
name = "cosmic-applet-network"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
<!DOCTYPE busconfig PUBLIC
|
|
||||||
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
|
||||||
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
|
||||||
<busconfig>
|
|
||||||
<policy context="default">
|
|
||||||
<allow own_prefix="com.system76.CosmicSettings.Applet"/>
|
|
||||||
</policy>
|
|
||||||
</busconfig>
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
cancel = Têk bibe
|
|
||||||
connect = Girê bide
|
|
||||||
connected = Hat girêdan
|
|
||||||
network = Tor
|
|
||||||
airplane-mode = Awaya balafirê
|
|
||||||
airplane-mode-on = Awaya balafirê vekirî ye
|
|
||||||
turn-off-airplane-mode = Ji bo çalakirina Wi-Fi, Bluetooth û torên mobayîl, awaya balafirê bigire.
|
|
||||||
wifi = Wi-Fi
|
|
||||||
identity = Nasname
|
|
||||||
ipv4 = IPv4
|
|
||||||
ipv6 = IPv6
|
|
||||||
mac = MAC
|
|
||||||
megabits-per-second = Mbps
|
|
||||||
gigabits-per-second = Gbps
|
|
||||||
terabits-per-second = Tbps
|
|
||||||
connecting = Tê girêdan
|
|
||||||
settings = Sazkariyên torê...
|
|
||||||
visible-wireless-networks = Torên bêtêl ên xuya
|
|
||||||
vpn-connections = Girêdanên VPN
|
|
||||||
enter-password = Borînpeyvê yan jî kilîtê şîfrekirinê têxîne
|
|
||||||
router-wps-button = Tu dikarî bi pêlkirina bişkoka "WPS" ya li ser routerê jî girê bidî
|
|
||||||
unable-to-connect = Nikare bi torê ve were girêdan
|
|
||||||
check-wifi-connection = Piştrast bike ku Wi-Fi bi înternetê ve girêdayî ye û borînpeyv rast e
|
|
||||||
reset = Ji nû ve saz bike
|
|
||||||
|
|
@ -35,7 +35,7 @@ use cosmic::{
|
||||||
widget::{
|
widget::{
|
||||||
Id, button, column, container, divider,
|
Id, button, column, container, divider,
|
||||||
icon::{self, from_name},
|
icon::{self, from_name},
|
||||||
indeterminate_circular, row, scrollable, secure_input, text, text_input, toggler,
|
row, scrollable, secure_input, text, text_input, toggler,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use cosmic_dbus_networkmanager::interface::enums::{
|
use cosmic_dbus_networkmanager::interface::enums::{
|
||||||
|
|
@ -405,7 +405,6 @@ fn secret_agent_task(identifier: String) -> Task<NmAgentEvent> {
|
||||||
cosmic::Task::stream(async_fn_stream::fn_stream(move |emitter| async move {
|
cosmic::Task::stream(async_fn_stream::fn_stream(move |emitter| async move {
|
||||||
let registration = SecretAgent::builder()
|
let registration = SecretAgent::builder()
|
||||||
.with_identifier(identifier)
|
.with_identifier(identifier)
|
||||||
.with_object_path("/org/freedesktop/NetworkManager/SecretAgent")
|
|
||||||
.with_capabilities(SecretAgentCapabilities::VPN_HINTS)
|
.with_capabilities(SecretAgentCapabilities::VPN_HINTS)
|
||||||
.register()
|
.register()
|
||||||
.await;
|
.await;
|
||||||
|
|
@ -665,13 +664,6 @@ fn load_vpns(_conn: zbus::Connection) -> Task<crate::app::Message> {
|
||||||
|
|
||||||
let mut map: IndexMap<UUID, ConnectionSettings> = IndexMap::new();
|
let mut map: IndexMap<UUID, ConnectionSettings> = IndexMap::new();
|
||||||
for c in saved {
|
for c in saved {
|
||||||
// Skip in-memory-only NM connections — assumed connections that NM
|
|
||||||
// auto-generated from externally-managed interfaces (e.g. one
|
|
||||||
// brought up by wg-quick@wg0.service) report unsaved=true and
|
|
||||||
// evaporate on deactivate, leaving the applet's toggle dead.
|
|
||||||
if c.unsaved {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let uuid: UUID = Arc::from(c.uuid.as_str());
|
let uuid: UUID = Arc::from(c.uuid.as_str());
|
||||||
let entry = match c.summary {
|
let entry = match c.summary {
|
||||||
SettingsSummary::WireGuard { .. } => ConnectionSettings::Wireguard { id: c.id },
|
SettingsSummary::WireGuard { .. } => ConnectionSettings::Wireguard { id: c.id },
|
||||||
|
|
@ -734,10 +726,9 @@ impl cosmic::Application for CosmicNetworkApplet {
|
||||||
tasks.push(update_state(conn.clone()));
|
tasks.push(update_state(conn.clone()));
|
||||||
tasks.push(update_devices(conn.clone()));
|
tasks.push(update_devices(conn.clone()));
|
||||||
tasks.push(load_vpns(conn));
|
tasks.push(load_vpns(conn));
|
||||||
let uuid = uuid::Uuid::new_v4().to_string().replace("-", "_");
|
|
||||||
|
|
||||||
let my_id = format!(
|
let my_id = format!(
|
||||||
"com.system76.CosmicSettings.Applet._{uuid}.NetworkManager.SecretAgent",
|
"com.system76.CosmicSettings.Applet.{}.NetworkManager.SecretAgent",
|
||||||
|
uuid::Uuid::new_v4()
|
||||||
);
|
);
|
||||||
tasks.push(secret_agent_task(my_id).map(Message::SecretAgent));
|
tasks.push(secret_agent_task(my_id).map(Message::SecretAgent));
|
||||||
}
|
}
|
||||||
|
|
@ -1516,7 +1507,12 @@ impl cosmic::Application for CosmicNetworkApplet {
|
||||||
];
|
];
|
||||||
match state {
|
match state {
|
||||||
ActiveConnectionState::Activating | ActiveConnectionState::Deactivating => {
|
ActiveConnectionState::Activating | ActiveConnectionState::Deactivating => {
|
||||||
btn_content.push(indeterminate_circular().size(24.0).into());
|
btn_content.push(
|
||||||
|
icon::from_name("process-working-symbolic")
|
||||||
|
.size(24)
|
||||||
|
.symbolic(true)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ActiveConnectionState::Activated => btn_content.push(
|
ActiveConnectionState::Activated => btn_content.push(
|
||||||
text::body(fl!("connected"))
|
text::body(fl!("connected"))
|
||||||
|
|
@ -1724,7 +1720,12 @@ impl cosmic::Application for CosmicNetworkApplet {
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
btn_content.push(ssid.into());
|
btn_content.push(ssid.into());
|
||||||
btn_content.push(indeterminate_circular().size(24.0).into());
|
btn_content.push(
|
||||||
|
icon::from_name("process-working-symbolic")
|
||||||
|
.size(24)
|
||||||
|
.symbolic(true)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
} else if matches!(known.state, DeviceState::Unavailable) {
|
} else if matches!(known.state, DeviceState::Unavailable) {
|
||||||
btn_content.push(
|
btn_content.push(
|
||||||
icon::from_name("network-wireless-disconnected-symbolic")
|
icon::from_name("network-wireless-disconnected-symbolic")
|
||||||
|
|
@ -1896,7 +1897,10 @@ impl cosmic::Application for CosmicNetworkApplet {
|
||||||
let connecting = padded_control(
|
let connecting = padded_control(
|
||||||
row::with_children([
|
row::with_children([
|
||||||
Element::from(id),
|
Element::from(id),
|
||||||
indeterminate_circular().size(24.0).into(),
|
icon::from_name("process-working-symbolic")
|
||||||
|
.size(24)
|
||||||
|
.symbolic(true)
|
||||||
|
.into(),
|
||||||
])
|
])
|
||||||
.spacing(8),
|
.spacing(8),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-applet-notifications"
|
name = "cosmic-applet-notifications"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
hours-ago =
|
|
||||||
{ $duration ->
|
|
||||||
[0] Niha
|
|
||||||
[one] 1 demjimêr berê
|
|
||||||
*[other] { $duration } demjimêr berê
|
|
||||||
}
|
|
||||||
minutes-ago =
|
|
||||||
{ $duration ->
|
|
||||||
[0] Niha
|
|
||||||
[one] 1 xulek berê
|
|
||||||
*[other] { $duration } xulek berê
|
|
||||||
}
|
|
||||||
show-less = Kêmtir nîşan bide
|
|
||||||
show-more = Bêtir { $more } nîşan bide
|
|
||||||
clear-group = Komê pak bike
|
|
||||||
clear-all = Hemû agahdariyan pak bike
|
|
||||||
do-not-disturb = Rawestandina balkêşiyê
|
|
||||||
notification-settings = Sazkariyên agahdariyê....
|
|
||||||
no-notifications = Agahdarî tune ne
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-applet-power"
|
name = "cosmic-applet-power"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
cancel = Têk bibe
|
|
||||||
confirm = Bipejirîne
|
|
||||||
power = Hêz
|
|
||||||
restart = Ji nû ve bide destpêkirin
|
|
||||||
settings = Sazkarî...
|
|
||||||
shutdown = Vemrîne
|
|
||||||
suspend = Rawestîne
|
|
||||||
log-out = Derkeve
|
|
||||||
lock-screen = Dîmenderê kilît bike
|
|
||||||
lock-screen-shortcut = Super + Escape
|
|
||||||
log-out-shortcut = Super + Shift + Escape
|
|
||||||
confirm-button =
|
|
||||||
{ $action ->
|
|
||||||
[restart] { restart }
|
|
||||||
[suspend] { suspend }
|
|
||||||
[shutdown] Vemrîne
|
|
||||||
[log-out] { log-out }
|
|
||||||
*[other] { confirm }
|
|
||||||
}
|
|
||||||
confirm-title =
|
|
||||||
{ $action ->
|
|
||||||
[restart] { restart }
|
|
||||||
[suspend] { suspend }
|
|
||||||
[shutdown] { shutdown }
|
|
||||||
[log-out] Hemû sepanan bigire û derkeve
|
|
||||||
*[other] Çalakiyê hilbijartî bisepîne
|
|
||||||
} niha?
|
|
||||||
confirm-body =
|
|
||||||
Pergal wê xweber were { $action ->
|
|
||||||
[restart] jinûvedestpêkkirin
|
|
||||||
[suspend] rawestandin
|
|
||||||
[shutdown] vemirandin
|
|
||||||
[lock-screen] kilîtkirin
|
|
||||||
[log-out] derketin
|
|
||||||
*[other] çalakiyê hilbijartî bisepîne
|
|
||||||
}di { $countdown } çirke de.
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-applet-status-area"
|
name = "cosmic-applet-status-area"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,10 @@ use cosmic::{
|
||||||
},
|
},
|
||||||
cctk::sctk::reexports::calloop,
|
cctk::sctk::reexports::calloop,
|
||||||
iced::{
|
iced::{
|
||||||
self,
|
self, Length, Subscription,
|
||||||
Event::Mouse,
|
|
||||||
Length, Subscription, event,
|
|
||||||
mouse::{self, ScrollDelta},
|
|
||||||
platform_specific::shell::commands::popup::{destroy_popup, get_popup},
|
platform_specific::shell::commands::popup::{destroy_popup, get_popup},
|
||||||
window,
|
window,
|
||||||
},
|
},
|
||||||
scroll::{DiscreteScrollDelta, DiscreteScrollState},
|
|
||||||
surface,
|
surface,
|
||||||
widget::{container, mouse_area},
|
widget::{container, mouse_area},
|
||||||
};
|
};
|
||||||
|
|
@ -37,8 +33,7 @@ pub enum Msg {
|
||||||
StatusMenu((usize, status_menu::Msg)),
|
StatusMenu((usize, status_menu::Msg)),
|
||||||
StatusNotifier(status_notifier_watcher::Event),
|
StatusNotifier(status_notifier_watcher::Event),
|
||||||
TogglePopup(usize),
|
TogglePopup(usize),
|
||||||
Hovered(Option<usize>),
|
Hovered(usize),
|
||||||
WheelScrolled(ScrollDelta),
|
|
||||||
Surface(surface::Action),
|
Surface(surface::Action),
|
||||||
ToggleOverflow,
|
ToggleOverflow,
|
||||||
HoveredOverflow,
|
HoveredOverflow,
|
||||||
|
|
@ -54,8 +49,6 @@ pub(crate) struct App {
|
||||||
max_menu_id: usize,
|
max_menu_id: usize,
|
||||||
popup: Option<window::Id>,
|
popup: Option<window::Id>,
|
||||||
overflow_popup: Option<window::Id>,
|
overflow_popup: Option<window::Id>,
|
||||||
hovered_menu: Option<usize>,
|
|
||||||
scroll_states: BTreeMap<usize, DiscreteScrollState>,
|
|
||||||
token_tx: Option<calloop::channel::Sender<TokenRequest>>,
|
token_tx: Option<calloop::channel::Sender<TokenRequest>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,10 +103,9 @@ impl App {
|
||||||
let overflow_index = self.overflow_index().unwrap_or(0);
|
let overflow_index = self.overflow_index().unwrap_or(0);
|
||||||
let children = self.menus.iter().skip(overflow_index).map(|(id, menu)| {
|
let children = self.menus.iter().skip(overflow_index).map(|(id, menu)| {
|
||||||
mouse_area(
|
mouse_area(
|
||||||
menu_icon_button(&self.core.applet, menu).on_press_down(Msg::TogglePopup(*id)),
|
menu_icon_button(&self.core.applet, &menu).on_press_down(Msg::TogglePopup(*id)),
|
||||||
)
|
)
|
||||||
.on_enter(Msg::Hovered(Some(*id)))
|
.on_enter(Msg::Hovered(*id))
|
||||||
.on_exit(Msg::Hovered(None))
|
|
||||||
.into()
|
.into()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -218,7 +210,6 @@ impl cosmic::Application for App {
|
||||||
{
|
{
|
||||||
let id = *id;
|
let id = *id;
|
||||||
self.menus.remove(&id);
|
self.menus.remove(&id);
|
||||||
self.scroll_states.remove(&id);
|
|
||||||
if self.open_menu == Some(id) {
|
if self.open_menu == Some(id) {
|
||||||
self.open_menu = None;
|
self.open_menu = None;
|
||||||
if let Some(popup_id) = self.popup {
|
if let Some(popup_id) = self.popup {
|
||||||
|
|
@ -321,10 +312,6 @@ impl cosmic::Application for App {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Msg::Hovered(id) => {
|
Msg::Hovered(id) => {
|
||||||
self.hovered_menu = id;
|
|
||||||
let Some(id) = id else {
|
|
||||||
return Task::none();
|
|
||||||
};
|
|
||||||
let mut cmds = Vec::new();
|
let mut cmds = Vec::new();
|
||||||
if let Some(old_id) = self.open_menu.take() {
|
if let Some(old_id) = self.open_menu.take() {
|
||||||
if old_id != id {
|
if old_id != id {
|
||||||
|
|
@ -374,19 +361,6 @@ impl cosmic::Application for App {
|
||||||
cmds.push(get_popup(popup_settings));
|
cmds.push(get_popup(popup_settings));
|
||||||
Task::batch(cmds)
|
Task::batch(cmds)
|
||||||
}
|
}
|
||||||
Msg::WheelScrolled(delta) => {
|
|
||||||
let Some(id) = self.hovered_menu else {
|
|
||||||
return Task::none();
|
|
||||||
};
|
|
||||||
let discrete_delta = self.scroll_states.entry(id).or_default().update(delta);
|
|
||||||
let Some((delta, orientation)) = discrete_scroll_delta(discrete_delta) else {
|
|
||||||
return Task::none();
|
|
||||||
};
|
|
||||||
let Some(menu) = self.menus.get(&id) else {
|
|
||||||
return Task::none();
|
|
||||||
};
|
|
||||||
scroll(id, menu.item.item_proxy().clone(), delta, orientation)
|
|
||||||
}
|
|
||||||
Msg::Surface(a) => {
|
Msg::Surface(a) => {
|
||||||
return cosmic::task::message(cosmic::Action::Cosmic(
|
return cosmic::task::message(cosmic::Action::Cosmic(
|
||||||
cosmic::app::Action::Surface(a),
|
cosmic::app::Action::Surface(a),
|
||||||
|
|
@ -431,7 +405,6 @@ impl cosmic::Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Msg::HoveredOverflow => {
|
Msg::HoveredOverflow => {
|
||||||
self.hovered_menu = None;
|
|
||||||
let mut cmds = Vec::new();
|
let mut cmds = Vec::new();
|
||||||
if self.overflow_popup.is_some() {
|
if self.overflow_popup.is_some() {
|
||||||
// If we already have an overflow popup, do nothing
|
// If we already have an overflow popup, do nothing
|
||||||
|
|
@ -487,12 +460,6 @@ impl cosmic::Application for App {
|
||||||
subscriptions.push(menu.subscription(is_open).with(*id).map(Msg::StatusMenu));
|
subscriptions.push(menu.subscription(is_open).with(*id).map(Msg::StatusMenu));
|
||||||
}
|
}
|
||||||
subscriptions.push(activation_token_subscription(0).map(Msg::Token));
|
subscriptions.push(activation_token_subscription(0).map(Msg::Token));
|
||||||
subscriptions.push(event::listen_with(|e, status, _| match (e, status) {
|
|
||||||
(Mouse(mouse::Event::WheelScrolled { delta }), event::Status::Ignored) => {
|
|
||||||
Some(Msg::WheelScrolled(delta))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}));
|
|
||||||
|
|
||||||
iced::Subscription::batch(subscriptions)
|
iced::Subscription::batch(subscriptions)
|
||||||
}
|
}
|
||||||
|
|
@ -505,10 +472,9 @@ impl cosmic::Application for App {
|
||||||
.iter()
|
.iter()
|
||||||
.take(overflow_index.unwrap_or(self.menus.len()))
|
.take(overflow_index.unwrap_or(self.menus.len()))
|
||||||
.map(|(id, menu)| {
|
.map(|(id, menu)| {
|
||||||
mouse_area(menu_icon_button(&self.core.applet, menu).on_press(Msg::Activate(*id)))
|
mouse_area(menu_icon_button(&self.core.applet, &menu).on_press(Msg::Activate(*id)))
|
||||||
.on_right_press(Msg::TogglePopup(*id))
|
.on_right_press(Msg::TogglePopup(*id))
|
||||||
.on_enter(Msg::Hovered(Some(*id)))
|
.on_enter(Msg::Hovered(*id))
|
||||||
.on_exit(Msg::Hovered(None))
|
|
||||||
.into()
|
.into()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -604,33 +570,6 @@ fn activate(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll(
|
|
||||||
id: usize,
|
|
||||||
item_proxy: crate::subscriptions::status_notifier_item::StatusNotifierItemProxy<'static>,
|
|
||||||
delta: i32,
|
|
||||||
orientation: &'static str,
|
|
||||||
) -> Task<cosmic::Action<Msg>> {
|
|
||||||
Task::future(async move {
|
|
||||||
match item_proxy.scroll(delta, orientation).await {
|
|
||||||
Ok(_) => cosmic::action::app(Msg::None),
|
|
||||||
Err(err) => {
|
|
||||||
tracing::error!("Scroll failed for {}: {}", id, err);
|
|
||||||
cosmic::action::app(Msg::None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn discrete_scroll_delta(delta: DiscreteScrollDelta) -> Option<(i32, &'static str)> {
|
|
||||||
if delta.y != 0 {
|
|
||||||
Some((delta.y as i32, "vertical"))
|
|
||||||
} else if delta.x != 0 {
|
|
||||||
Some((delta.x as i32, "horizontal"))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn menu_icon_button<'a>(
|
fn menu_icon_button<'a>(
|
||||||
applet: &'a cosmic::applet::Context,
|
applet: &'a cosmic::applet::Context,
|
||||||
menu: &'a status_menu::State,
|
menu: &'a status_menu::State,
|
||||||
|
|
@ -675,43 +614,3 @@ fn menu_icon_button<'a>(
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
cosmic::applet::run::<App>(())
|
cosmic::applet::run::<App>(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn discrete_scroll_prefers_vertical_delta() {
|
|
||||||
assert_eq!(
|
|
||||||
discrete_scroll_delta(cosmic::scroll::DiscreteScrollDelta { x: 4, y: -2 }),
|
|
||||||
Some((-2, "vertical"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn discrete_scroll_uses_horizontal_delta_when_vertical_is_zero() {
|
|
||||||
assert_eq!(
|
|
||||||
discrete_scroll_delta(cosmic::scroll::DiscreteScrollDelta { x: 3, y: 0 }),
|
|
||||||
Some((3, "horizontal"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn discrete_scroll_ignores_zero_delta() {
|
|
||||||
assert_eq!(
|
|
||||||
discrete_scroll_delta(cosmic::scroll::DiscreteScrollDelta { x: 0, y: 0 }),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pixel_scroll_accumulates_before_emitting_discrete_delta() {
|
|
||||||
let mut state = cosmic::scroll::DiscreteScrollState::default();
|
|
||||||
|
|
||||||
let first = state.update(ScrollDelta::Pixels { x: 0.0, y: 12.0 });
|
|
||||||
assert_eq!(discrete_scroll_delta(first), None);
|
|
||||||
|
|
||||||
let second = state.update(ScrollDelta::Pixels { x: 0.0, y: 12.0 });
|
|
||||||
assert_eq!(discrete_scroll_delta(second), Some((1, "vertical")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -202,8 +202,6 @@ pub trait StatusNotifierItem {
|
||||||
fn activate(&self, x: i32, y: i32) -> zbus::Result<()>;
|
fn activate(&self, x: i32, y: i32) -> zbus::Result<()>;
|
||||||
|
|
||||||
fn secondary_activate(&self, x: i32, y: i32) -> zbus::Result<()>;
|
fn secondary_activate(&self, x: i32, y: i32) -> zbus::Result<()>;
|
||||||
|
|
||||||
fn scroll(&self, delta: i32, orientation: &str) -> zbus::Result<()>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-applet-tiling"
|
name = "cosmic-applet-tiling"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
tile-windows = Çarçoveyan xweber berhev bike
|
|
||||||
tile-current = Qada xebatê heyî berhev bike
|
|
||||||
shortcuts = Kurterê
|
|
||||||
navigate-windows = Di nav çarçoveyan de bigere
|
|
||||||
move-window = Çarçoveyê bilivîne
|
|
||||||
toggle-floating-window = Rewşê çarçoveyê herikbar biguhêrîne
|
|
||||||
view-all-shortcuts = Hemû kurteriyan nîşan bide...
|
|
||||||
active-hint = Pêşniyara çalak
|
|
||||||
gaps = Valahî
|
|
||||||
floating-window-exceptions = Awarteyên çarçoveyê herikbar...
|
|
||||||
window-management-settings = Sazkariyên rêveberiya çarçoveyê...
|
|
||||||
all-workspaces = Hemû qadên xebatê
|
|
||||||
per-workspace = Li gorî qada xebatê
|
|
||||||
super = Super
|
|
||||||
shift = Shift
|
|
||||||
arrow-keys = tîr
|
|
||||||
tiled = Berhevkirî
|
|
||||||
floating = Herikbar
|
|
||||||
autotile-behavior = Li ser qadên xebatê çarçoveyan berhev bike
|
|
||||||
new-workspace = Tevgera qada xebatê ya nû
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-applet-time"
|
name = "cosmic-applet-time"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
datetime-settings = Sazkariyên dîrok, dem û salnameyê...
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-applet-workspaces"
|
name = "cosmic-applet-workspaces"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
authors = ["Ashley Wulber <ashley@system76.com>"]
|
authors = ["Ashley Wulber <ashley@system76.com>"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
cosmic-applet-workspaces = Qadên xebatê yên COSMIC
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-applets-config"
|
name = "cosmic-applets-config"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-applets"
|
name = "cosmic-applets"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-panel-button"
|
name = "cosmic-panel-button"
|
||||||
version = "1.0.15"
|
version = "1.0.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
|
|
|
||||||
6
debian/changelog
vendored
6
debian/changelog
vendored
|
|
@ -1,9 +1,3 @@
|
||||||
cosmic-applets (1.0.15) noble; urgency=medium
|
|
||||||
|
|
||||||
* Update changelog
|
|
||||||
|
|
||||||
-- Michael Murphy <mmstick@pop-os> Thu, 28 May 2026 19:33:02 +0200
|
|
||||||
|
|
||||||
cosmic-applets (1.0.12) noble; urgency=medium
|
cosmic-applets (1.0.12) noble; urgency=medium
|
||||||
|
|
||||||
* Update changelog
|
* Update changelog
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
cosmic-applet-a11y = アクセシビリティ
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
cosmic-applet-audio = Deng
|
|
||||||
cosmic-app-list = Destgeha sepanan
|
|
||||||
cosmic-applet-a11y = Gihîştin
|
|
||||||
cosmic-applet-bluetooth = Bluetooth
|
|
||||||
cosmic-applet-bluetooth-comment = Amûrên Bluetooth bi rê ve bibe
|
|
||||||
cosmic-applet-bluetooth-keywords = COSMIC;Sepanok;Bluetooth;
|
|
||||||
cosmic-applet-input-sources = Çavkaniyên ketanê
|
|
||||||
cosmic-applet-input-sources-comment = Di navbera çavkaniyên ketanê de biguhêrîne
|
|
||||||
cosmic-applet-input-sources-keywords = COSMIC;Sepanok;Ketan;Çavkanî;
|
|
||||||
cosmic-app-list-comment = Sepanên darxistî veke û çarçoveyên vekirî bi rê ve bibe
|
|
||||||
cosmic-app-list-keywords = COSMIC;Sepanok;Sepan;Destgeh;Lîste;Erk;Bend;
|
|
||||||
cosmic-applet-a11y-comment = Sazkariyên gihîştinê ji destgehê saz bike
|
|
||||||
cosmic-applet-a11y-keywords = COSMIC;Sepanok;Gihîştin;Dîmender;A11y;Xwîner;Dîmender;Mezinker;Bijberî;Reng;
|
|
||||||
cosmic-applet-audio-comment = Hilbijartina amûra dengê, kontrola dengê, û kontrolên mediyaya MPRIS
|
|
||||||
cosmic-applet-audio-keywords = COSMIC;Sepanok;Deng;Deng;MPRIS;
|
|
||||||
cosmic-applet-battery = Hêz & Betarî
|
|
||||||
cosmic-applet-battery-comment = Awayên hêzê û vebijêrkên parastina hêzê
|
|
||||||
cosmic-applet-battery-keywords = COSMIC;Sepanok;Hêz;Betarî;
|
|
||||||
cosmic-applet-minimize = Çarçoveyên biçûk
|
|
||||||
cosmic-applet-minimize-comment = Çarçoveyên biçûk bi rê ve bibe
|
|
||||||
cosmic-applet-minimize-keywords = COSMIC;Sepanok;Biçûkkirin;
|
|
||||||
cosmic-applet-network = Tor
|
|
||||||
cosmic-applet-network-comment = Girêdanên torê bi rê ve bibe
|
|
||||||
cosmic-applet-network-keywords = COSMIC;Sepanok;Tor;
|
|
||||||
cosmic-applet-notifications = Navenda Agahdariyan
|
|
||||||
cosmic-applet-notifications-comment = Agahdarî û rawestandina balkişiyê bi rê ve bibe
|
|
||||||
cosmic-applet-notifications-keywords = COSMIC;Sepanok;Agahdarî;
|
|
||||||
cosmic-applet-power = Danaşîna bikarhêner
|
|
||||||
cosmic-applet-power-comment = Dîmenderê kilît bike, derkeve, rawestîne, ji nû ve bide destpêkirin, û vemrîne
|
|
||||||
cosmic-applet-power-keywords = COSMIC;Sepanok;Bikarhêner;Danaşîn;Kilîtkirin;Derketin;Destpêkirin;Vemirandin;Rawestandin;
|
|
||||||
cosmic-applet-status-area = Desgeha agahdariyan
|
|
||||||
cosmic-applet-status-area-comment = Nîşanderên sepanan ên ku dibe ku menuyan bo destgehê derxînin
|
|
||||||
cosmic-applet-status-area-keywords = COSMIC;Sepanok;Sepan;Nîşander;Agahdarî;Destgeh;Rewş;
|
|
||||||
cosmic-applet-tiling = Berhevkirin
|
|
||||||
cosmic-applet-tiling-comment = Berhevkirina xweber a çalak, ya niha û ya her qada xebatê bi rê ve bibe
|
|
||||||
cosmic-applet-tiling-keywords = COSMIC;Sepanok;Berhevkirin;Nîşander;Navenda xebatê;
|
|
||||||
cosmic-applet-time = Dîrok, dem & Salname
|
|
||||||
cosmic-applet-time-comment = Demê niha li ser destgehê bi salnameyeke vebûyî nîşan bide
|
|
||||||
cosmic-applet-time-keywords = COSMIC;Sepanok;Dîrok;Dem;Salname;
|
|
||||||
cosmic-applet-workspaces = Qada xebatê jimarkirî
|
|
||||||
cosmic-applet-workspaces-comment = Di navbera qada xebatê jimarkirî de di destgehê de biguherîne
|
|
||||||
cosmic-applet-workspaces-keywords = COSMIC;Sepanok;Qada xebatê;
|
|
||||||
cosmic-panel-app-button = Bişkoka pirtûkxaneya sepanan
|
|
||||||
cosmic-panel-app-button-comment = Pirtûkxaneya sepanan ji bo vekirina sepanên sazkirî veke
|
|
||||||
cosmic-panel-app-button-keywords = COSMIC;Sepanok;Sepan;Pirtûkxane;Destgeh;
|
|
||||||
cosmic-panel-launcher-button = Bişkoka destpêker
|
|
||||||
cosmic-panel-launcher-button-comment = Destpêkerê ji bo lêgerîna sepanan û xebitandina fermanan veke
|
|
||||||
cosmic-panel-launcher-button-keywords = COSMIC;Sepanok;Destpêker;Rêveber;
|
|
||||||
cosmic-panel-workspaces-button = Bişkoka qadên xebatê
|
|
||||||
cosmic-panel-workspaces-button-comment = Pêşdîtina qadên xebatê veke ji bo bi rê ve bibî û biguhêrînî
|
|
||||||
cosmic-panel-workspaces-button-keywords = COSMIC;Sepan;Qada xebatê;Pêşdîtin;
|
|
||||||
|
|
@ -46,6 +46,3 @@ cosmic-panel-launcher-button-keywords = COSMIC;애플릿;런처;실행기;
|
||||||
cosmic-panel-workspaces-button = 작업 공간 버튼
|
cosmic-panel-workspaces-button = 작업 공간 버튼
|
||||||
cosmic-panel-workspaces-button-comment = 작업 공간 관리 및 전환을 위해 작업 공간 개요 열기
|
cosmic-panel-workspaces-button-comment = 작업 공간 관리 및 전환을 위해 작업 공간 개요 열기
|
||||||
cosmic-panel-workspaces-button-keywords = COSMIC;애플릿;작업 공간;개요;
|
cosmic-panel-workspaces-button-keywords = COSMIC;애플릿;작업 공간;개요;
|
||||||
cosmic-applet-workspaces-comment = 패널에서 번호가 지정된 작업 공간 간 전환
|
|
||||||
cosmic-applet-tiling-comment = 활성 창 강조, 현재 및 작업 공간별 자동 타일링 관리
|
|
||||||
cosmic-applet-workspaces = 번호가 지정된 작업 공간
|
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,3 @@ cosmic-applet-workspaces-comment = Alternar entre áreas de trabalho numeradas n
|
||||||
cosmic-applet-workspaces-keywords = COSMIC;Miniaplicação;Applet;Área;Trabalho;
|
cosmic-applet-workspaces-keywords = COSMIC;Miniaplicação;Applet;Área;Trabalho;
|
||||||
cosmic-panel-workspaces-button-comment = Abrir a visão geral das áreas de trabalho para gerir e alternar entre elas
|
cosmic-panel-workspaces-button-comment = Abrir a visão geral das áreas de trabalho para gerir e alternar entre elas
|
||||||
cosmic-panel-workspaces-button-keywords = COSMIC;Miniaplicação;Applet;Área;Trabalho;Visão;Geral;
|
cosmic-panel-workspaces-button-keywords = COSMIC;Miniaplicação;Applet;Área;Trabalho;Visão;Geral;
|
||||||
cosmic-applet-a11y-comment = Configurar definições de acessibilidade a partir do painel
|
|
||||||
cosmic-applet-battery = Energia e Bateria
|
|
||||||
cosmic-app-list-keywords = COSMIC;Miniaplicação;Applet;Aplicação;Bandeja;Lista;Tarefa;Barra;
|
|
||||||
cosmic-applet-a11y-keywords = COSMIC;Miniaplicação;Applet;Acessibilidade;A11y;Ecrã;Leitor;Lupa;Ampliador;Contraste;Cor;
|
|
||||||
cosmic-applet-audio-keywords = COSMIC;Miniaplicação;Applet;Som;Áudio;MPRIS;
|
|
||||||
|
|
|
||||||
7
justfile
7
justfile
|
|
@ -59,16 +59,13 @@ _install_status_notifier_watcher:
|
||||||
sed "s|@bindir@|{{ prefixdir }}|" cosmic-applet-status-area/data/com.system76.CosmicStatusNotifierWatcher.service.in > cosmic-applet-status-area/data/com.system76.CosmicStatusNotifierWatcher.service
|
sed "s|@bindir@|{{ prefixdir }}|" cosmic-applet-status-area/data/com.system76.CosmicStatusNotifierWatcher.service.in > cosmic-applet-status-area/data/com.system76.CosmicStatusNotifierWatcher.service
|
||||||
install -Dm0644 cosmic-applet-status-area/data/com.system76.CosmicStatusNotifierWatcher.service {{ libdir }}/systemd/user/com.system76.CosmicStatusNotifierWatcher.service
|
install -Dm0644 cosmic-applet-status-area/data/com.system76.CosmicStatusNotifierWatcher.service {{ libdir }}/systemd/user/com.system76.CosmicStatusNotifierWatcher.service
|
||||||
|
|
||||||
_install_secret_agent_policy:
|
|
||||||
install -Dm0644 cosmic-applet-network/data/dbus-1/system.d/com.system76.CosmicSettings.Applet.NetworkManager.SecretAgent.conf {{ sharedir }}/dbus-1/system.d/com.system76.CosmicSettings.Applet.NetworkManager.SecretAgent.conf
|
|
||||||
|
|
||||||
# Installs files into the system
|
# Installs files into the system
|
||||||
install: (_install_bin 'cosmic-applets') (_link_applet 'cosmic-panel-button') (_install_applet 'com.system76.CosmicAppList' 'cosmic-app-list') (_install_default_schema 'cosmic-app-list') (_install_applet 'com.system76.CosmicAppletA11y' 'cosmic-applet-a11y') (_install_applet 'com.system76.CosmicAppletAudio' 'cosmic-applet-audio') (_install_applet 'com.system76.CosmicAppletInputSources' 'cosmic-applet-input-sources') (_install_applet 'com.system76.CosmicAppletBattery' 'cosmic-applet-battery') (_install_applet 'com.system76.CosmicAppletBluetooth' 'cosmic-applet-bluetooth') (_install_applet 'com.system76.CosmicAppletMinimize' 'cosmic-applet-minimize') (_install_applet 'com.system76.CosmicAppletNetwork' 'cosmic-applet-network') (_install_applet 'com.system76.CosmicAppletNotifications' 'cosmic-applet-notifications') (_install_applet 'com.system76.CosmicAppletPower' 'cosmic-applet-power') (_install_applet 'com.system76.CosmicAppletStatusArea' 'cosmic-applet-status-area') (_install_applet 'com.system76.CosmicAppletTiling' 'cosmic-applet-tiling') (_install_applet 'com.system76.CosmicAppletTime' 'cosmic-applet-time') (_install_applet 'com.system76.CosmicAppletWorkspaces' 'cosmic-applet-workspaces') (_install_button 'com.system76.CosmicPanelAppButton' 'cosmic-panel-app-button') (_install_button 'com.system76.CosmicPanelLauncherButton' 'cosmic-panel-launcher-button') (_install_button 'com.system76.CosmicPanelWorkspacesButton' 'cosmic-panel-workspaces-button') _install_metainfo _install_status_notifier_watcher _install_secret_agent_policy
|
install: (_install_bin 'cosmic-applets') (_link_applet 'cosmic-panel-button') (_install_applet 'com.system76.CosmicAppList' 'cosmic-app-list') (_install_default_schema 'cosmic-app-list') (_install_applet 'com.system76.CosmicAppletA11y' 'cosmic-applet-a11y') (_install_applet 'com.system76.CosmicAppletAudio' 'cosmic-applet-audio') (_install_applet 'com.system76.CosmicAppletInputSources' 'cosmic-applet-input-sources') (_install_applet 'com.system76.CosmicAppletBattery' 'cosmic-applet-battery') (_install_applet 'com.system76.CosmicAppletBluetooth' 'cosmic-applet-bluetooth') (_install_applet 'com.system76.CosmicAppletMinimize' 'cosmic-applet-minimize') (_install_applet 'com.system76.CosmicAppletNetwork' 'cosmic-applet-network') (_install_applet 'com.system76.CosmicAppletNotifications' 'cosmic-applet-notifications') (_install_applet 'com.system76.CosmicAppletPower' 'cosmic-applet-power') (_install_applet 'com.system76.CosmicAppletStatusArea' 'cosmic-applet-status-area') (_install_applet 'com.system76.CosmicAppletTiling' 'cosmic-applet-tiling') (_install_applet 'com.system76.CosmicAppletTime' 'cosmic-applet-time') (_install_applet 'com.system76.CosmicAppletWorkspaces' 'cosmic-applet-workspaces') (_install_button 'com.system76.CosmicPanelAppButton' 'cosmic-panel-app-button') (_install_button 'com.system76.CosmicPanelLauncherButton' 'cosmic-panel-launcher-button') (_install_button 'com.system76.CosmicPanelWorkspacesButton' 'cosmic-panel-workspaces-button') _install_metainfo _install_status_notifier_watcher
|
||||||
|
|
||||||
# Vendor Cargo dependencies locally
|
# Vendor Cargo dependencies locally
|
||||||
vendor:
|
vendor:
|
||||||
mkdir -p .cargo
|
mkdir -p .cargo
|
||||||
cargo vendor --locked | head -n -1 > .cargo/config
|
cargo vendor | head -n -1 > .cargo/config
|
||||||
echo 'directory = "vendor"' >> .cargo/config
|
echo 'directory = "vendor"' >> .cargo/config
|
||||||
tar pcf vendor.tar vendor
|
tar pcf vendor.tar vendor
|
||||||
rm -rf vendor
|
rm -rf vendor
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue