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 |
57 changed files with 2006 additions and 510 deletions
456
Cargo.lock
generated
456
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
54
Cargo.toml
54
Cargo.toml
|
|
@ -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,7 +59,7 @@ 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"] }
|
||||
|
||||
[profile.release]
|
||||
|
|
@ -81,12 +82,51 @@ ignored = ["libcosmic"]
|
|||
# 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-network-manager-subscription = { path = "/home/lionel/Projets/COSMIC/cosmic-settings/subscriptions/network-manager" }
|
||||
cosmic-settings-sound-subscription = { path = "/home/lionel/Projets/COSMIC/cosmic-settings/subscriptions/sound" }
|
||||
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" }
|
||||
cosmic-settings-network-manager-subscription = { path = "/home/lionel/Projets/COSMIC/cosmic-settings/subscriptions/network-manager" }
|
||||
|
||||
[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" }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,5 +6,5 @@ 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"] }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,3 +6,14 @@ new-window = Nouvelle fenêtre
|
|||
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
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
440
cosmic-app-list/src/icon_catalog.rs
Normal file
440
cosmic-app-list/src/icon_catalog.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
378
cosmic-app-list/src/launcher_edit.rs
Normal file
378
cosmic-app-list/src/launcher_edit.rs
Normal 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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ license = "GPL-3.0-only"
|
|||
[dependencies]
|
||||
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
|
||||
|
|
@ -21,5 +20,4 @@ urlencoding = "2.1.3"
|
|||
zbus.workspace = true
|
||||
|
||||
[dependencies.cosmic-settings-sound-subscription]
|
||||
git = "https://github.com/pop-os/cosmic-settings"
|
||||
# path = "../../cosmic-settings/subscriptions/sound"
|
||||
path = "../../cosmic-settings/subscriptions/sound"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -487,11 +487,31 @@ 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;
|
||||
|
|
@ -499,7 +519,7 @@ impl cosmic::Application for Audio {
|
|||
|
||||
let new_volume = (self.model.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 playback_buttons = (!self.core.applet.suggested_bounds.as_ref().is_some_and(|c| {
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -172,6 +172,7 @@ impl CosmicBatteryApplet {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
enum Message {
|
||||
TogglePopup,
|
||||
CloseRequested(window::Id),
|
||||
|
|
@ -511,7 +512,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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ use cosmic::{
|
|||
widget::{button, divider, icon, scrollable, text},
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use std::{collections::HashMap, sync::LazyLock, time::Duration};
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
use tokio::sync::mpsc::Sender;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -63,6 +63,7 @@ impl CosmicBluetoothApplet {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
enum Message {
|
||||
TogglePopup,
|
||||
CloseRequested(window::Id),
|
||||
|
|
@ -145,7 +146,7 @@ impl cosmic::Application for CosmicBluetoothApplet {
|
|||
}
|
||||
Message::BluetoothEvent(e) => match e {
|
||||
BluerEvent::RequestResponse {
|
||||
req,
|
||||
req: _,
|
||||
state,
|
||||
err_msg,
|
||||
} => {
|
||||
|
|
|
|||
|
|
@ -149,8 +149,6 @@ pub fn bluetooth_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
|||
},
|
||||
|
||||
BluerSessionEvent::AgentEvent(e) => BluerEvent::AgentEvent(e),
|
||||
|
||||
_ => return,
|
||||
};
|
||||
|
||||
_ = output.send(message).await;
|
||||
|
|
@ -199,6 +197,7 @@ pub enum BluerRequest {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub enum BluerEvent {
|
||||
RequestResponse {
|
||||
req: BluerRequest,
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ edition = "2024"
|
|||
license = "GPL-3.0-only"
|
||||
|
||||
[dependencies]
|
||||
cosmic-comp-config = { git = "https://github.com/pop-os/cosmic-comp.git", rev = "5eb5af4" }
|
||||
cosmic-comp-config.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
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
mod localize;
|
||||
|
||||
use cosmic::iced::{Alignment, Length};
|
||||
use cosmic::{
|
||||
app,
|
||||
app::Core,
|
||||
|
|
@ -14,16 +13,14 @@ use cosmic::{
|
|||
iced::{
|
||||
Rectangle, Task,
|
||||
platform_specific::shell::commands::popup::{destroy_popup, get_popup},
|
||||
widget::{column, row},
|
||||
window::Id,
|
||||
},
|
||||
iced::{core::window, runtime::Appearance},
|
||||
iced::core::window,
|
||||
prelude::*,
|
||||
surface, theme,
|
||||
widget::{
|
||||
self, autosize,
|
||||
rectangle_tracker::{RectangleTracker, RectangleUpdate, rectangle_tracker_subscription},
|
||||
space,
|
||||
},
|
||||
};
|
||||
use cosmic_comp_config::CosmicCompConfig;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ anyhow.workspace = true
|
|||
i18n-embed-fl.workspace = true
|
||||
i18n-embed.workspace = true
|
||||
image = { version = "0.25.9", default-features = false }
|
||||
libcosmic.workspace = true
|
||||
cosmic.workspace = true
|
||||
memmap2 = "0.9.10"
|
||||
rust-embed.workspace = true
|
||||
rustix.workspace = true
|
||||
|
|
|
|||
|
|
@ -316,7 +316,7 @@ impl cosmic::Application for Minimize {
|
|||
} else {
|
||||
(cross_padding, major_padding)
|
||||
};
|
||||
let theme = self.core.system_theme().cosmic();
|
||||
let _theme = self.core.system_theme().cosmic();
|
||||
let icon_buttons = self.apps[..max_icon_count].iter().map(|app| {
|
||||
self.core
|
||||
.applet
|
||||
|
|
@ -420,7 +420,7 @@ impl cosmic::Application for Minimize {
|
|||
(cross_padding, major_padding)
|
||||
};
|
||||
let theme = self.core.system_theme().cosmic();
|
||||
let space_xxs = theme.space_xxs();
|
||||
let _space_xxs = theme.space_xxs();
|
||||
let icon_buttons = self.apps[max_icon_count..].iter().map(|app| {
|
||||
tooltip(
|
||||
Element::from(crate::window_image::WindowImage::new(
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ license = "GPL-3.0-or-later"
|
|||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-fn-stream = "0.3"
|
||||
cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings" }
|
||||
cosmic-dbus-networkmanager = { path = "../../dbus-settings-bindings/networkmanager" }
|
||||
futures.workspace = true
|
||||
futures-util.workspace = true
|
||||
i18n-embed-fl.workspace = true
|
||||
i18n-embed.workspace = true
|
||||
libcosmic = { workspace = true, features = [
|
||||
cosmic = { workspace = true, features = [
|
||||
"applet",
|
||||
"applet-token",
|
||||
"tokio",
|
||||
|
|
@ -34,5 +34,4 @@ nmrs = "3.1.3"
|
|||
|
||||
|
||||
[dependencies.cosmic-settings-network-manager-subscription]
|
||||
git = "https://github.com/pop-os/cosmic-settings/"
|
||||
# path = "../../cosmic-settings/subscriptions/network-manager"
|
||||
path = "../../cosmic-settings/subscriptions/network-manager"
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
network = Hálózat
|
||||
airplane-mode = Repülőgép-üzemmód
|
||||
airplane-mode-on = A repülőgép-üzemmód be van kapcsolva
|
||||
turn-off-airplane-mode = Kapcsold ki a Wi-Fi, Bluetooth és mobil szélessáv engedélyezéséhez.
|
||||
turn-off-airplane-mode = Kapcsold ki a Wi-Fi, Bluetooth és mobil széles sáv engedélyezéséhez.
|
||||
wifi = Wi-Fi
|
||||
identity = Azonosító
|
||||
ipv4 = IPv4-cím
|
||||
ipv6 = IPv6-cím
|
||||
mac = MAC
|
||||
megabits-per-second = Mbps
|
||||
connected = Csatlakoztatva
|
||||
connecting = Csatlakozás…
|
||||
connect = Csatlakozás
|
||||
connected = Kapcsolódva
|
||||
connecting = Kapcsolódás
|
||||
connect = Kapcsolódás
|
||||
cancel = Mégse
|
||||
settings = Hálózati beállítások…
|
||||
visible-wireless-networks = Látható vezeték nélküli hálózatok
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ license = "GPL-3.0-only"
|
|||
cosmic-notifications-util = { git = "https://github.com/pop-os/cosmic-notifications" }
|
||||
cosmic-notifications-config = { git = "https://github.com/pop-os/cosmic-notifications" }
|
||||
anyhow.workspace = true
|
||||
libcosmic.workspace = true
|
||||
cosmic.workspace = true
|
||||
tokio.workspace = true
|
||||
# cosmic-notifications-util = { path = "../../cosmic-notifications-daemon/cosmic-notifications-util" }
|
||||
# cosmic-notifications-config = { path = "../../cosmic-notifications-daemon/cosmic-notifications-config" }
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
hours-ago =
|
||||
{ $duration ->
|
||||
[0] Most
|
||||
[one] 1 órával ezelőtt
|
||||
*[other] { $duration } órával ezelőtt
|
||||
[0] Épp most
|
||||
[one] 1 órája
|
||||
*[other] { $duration } órája
|
||||
}
|
||||
minutes-ago =
|
||||
{ $duration ->
|
||||
[0] Most
|
||||
[one] 1 perccel ezelőtt
|
||||
*[other] { $duration } perccel ezelőtt
|
||||
[0] Épp most
|
||||
[one] 1 perce
|
||||
*[other] { $duration } perce
|
||||
}
|
||||
show-less = Kevesebb megjelenítése
|
||||
show-more = { $more } további megjelenítése
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ license = "GPL-3.0-only"
|
|||
[dependencies]
|
||||
i18n-embed-fl.workspace = true
|
||||
i18n-embed.workspace = true
|
||||
libcosmic.workspace = true
|
||||
cosmic.workspace = true
|
||||
logind-zbus = "5.3.2"
|
||||
rust-embed.workspace = true
|
||||
rustix.workspace = true
|
||||
|
|
|
|||
|
|
@ -1,2 +1,4 @@
|
|||
cancel = Cancel·lar
|
||||
confirm = Confirmar
|
||||
restart = Reinicia
|
||||
suspend = Suspèn
|
||||
|
|
|
|||
|
|
@ -33,4 +33,4 @@ confirm-body =
|
|||
[lock-screen] zárolni fogja a képernyőt
|
||||
[log-out] kijelentkezik
|
||||
*[other] alkalmazni fogja a kiválasztott műveletet
|
||||
} { $countdown } másodperc múlva
|
||||
} { $countdown } másodperc múlva.
|
||||
|
|
|
|||
|
|
@ -16,9 +16,8 @@ use cosmic::{
|
|||
window,
|
||||
},
|
||||
surface, theme,
|
||||
widget::{Space, button, divider, icon, space, text},
|
||||
widget::{button, divider, icon, space, text},
|
||||
};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use logind_zbus::{
|
||||
manager::ManagerProxy,
|
||||
|
|
@ -35,9 +34,6 @@ pub mod session_manager;
|
|||
|
||||
use crate::{cosmic_session::CosmicSessionProxy, session_manager::SessionManagerProxy};
|
||||
|
||||
static SUBSURFACE_ID: LazyLock<cosmic::widget::Id> =
|
||||
LazyLock::new(|| cosmic::widget::Id::new("subsurface"));
|
||||
|
||||
pub fn run() -> cosmic::iced::Result {
|
||||
localize::localize();
|
||||
|
||||
|
|
@ -49,7 +45,6 @@ struct Power {
|
|||
icon_name: String,
|
||||
popup: Option<window::Id>,
|
||||
token_tx: Option<calloop::channel::Sender<TokenRequest>>,
|
||||
subsurface_id: window::Id,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -75,6 +70,7 @@ impl PowerAction {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
enum Message {
|
||||
Action(PowerAction),
|
||||
TogglePopup,
|
||||
|
|
@ -104,7 +100,6 @@ impl cosmic::Application for Power {
|
|||
Self {
|
||||
core,
|
||||
icon_name: "system-shutdown-symbolic".to_string(),
|
||||
subsurface_id: window::Id::unique(),
|
||||
token_tx: None,
|
||||
popup: Option::default(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ license = "GPL-3.0-only"
|
|||
|
||||
[dependencies]
|
||||
futures.workspace = true
|
||||
libcosmic.workspace = true
|
||||
cosmic.workspace = true
|
||||
serde.workspace = true
|
||||
rustc-hash.workspace = true
|
||||
tokio.workspace = true
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ use cosmic::{
|
|||
iced::{
|
||||
self, Length, Subscription,
|
||||
platform_specific::shell::commands::popup::{destroy_popup, get_popup},
|
||||
theme::Style,
|
||||
window,
|
||||
},
|
||||
surface,
|
||||
|
|
@ -25,6 +24,7 @@ use crate::{
|
|||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Msg {
|
||||
None,
|
||||
Activate(usize),
|
||||
|
|
@ -577,7 +577,7 @@ fn menu_icon_button<'a>(
|
|||
let icon = menu.icon_handle().clone();
|
||||
|
||||
let theme = cosmic::theme::active();
|
||||
let theme = theme.cosmic();
|
||||
let _theme = theme.cosmic();
|
||||
|
||||
let suggested = applet.suggested_size(true);
|
||||
let padding = applet.suggested_padding(true).1;
|
||||
|
|
@ -595,7 +595,7 @@ fn menu_icon_button<'a>(
|
|||
.class(if symbolic {
|
||||
cosmic::theme::Svg::Custom(std::rc::Rc::new(|theme| {
|
||||
cosmic::iced::widget::svg::Style {
|
||||
color: Some(theme.cosmic().background.on.into()),
|
||||
color: Some(theme.cosmic().background(false).on.into()),
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ pub struct State {
|
|||
expanded: Option<i32>,
|
||||
// TODO handle icon with multiple sizes?
|
||||
icon_handle: icon::Handle,
|
||||
icon_theme_path: Option<PathBuf>,
|
||||
click_event: Option<(i32, bool)>,
|
||||
}
|
||||
|
||||
|
|
@ -39,6 +40,7 @@ impl State {
|
|||
icon_handle: icon::from_name("application-default")
|
||||
.prefer_svg(true)
|
||||
.handle(),
|
||||
icon_theme_path: None,
|
||||
click_event: None,
|
||||
},
|
||||
iced::Task::none(),
|
||||
|
|
@ -63,6 +65,7 @@ impl State {
|
|||
}
|
||||
Msg::Icon(update) => {
|
||||
let icon_name = update.name.unwrap_or_default();
|
||||
self.icon_theme_path = update.theme_path;
|
||||
|
||||
// Use the icon pixmap if an icon was not defined by name.
|
||||
if icon_name.is_empty() {
|
||||
|
|
@ -93,13 +96,18 @@ impl State {
|
|||
self.icon_handle = if Path::new(&icon_name).exists() {
|
||||
icon::from_path(Path::new(&icon_name).to_path_buf()).symbolic(true)
|
||||
} else {
|
||||
icon::from_name(icon_name)
|
||||
.prefer_svg(true)
|
||||
.fallback(Some(IconFallback::Names(vec![
|
||||
let mut builder = icon::from_name(icon_name).prefer_svg(true).fallback(Some(
|
||||
IconFallback::Names(vec![
|
||||
"application-default".into(),
|
||||
"application-x-executable".into(),
|
||||
])))
|
||||
.handle()
|
||||
]),
|
||||
));
|
||||
|
||||
if let Some(ref theme_path) = self.icon_theme_path {
|
||||
builder = builder.with_extra_paths(vec![theme_path.clone()]);
|
||||
}
|
||||
|
||||
builder.handle()
|
||||
};
|
||||
|
||||
iced::Task::none()
|
||||
|
|
|
|||
|
|
@ -10,21 +10,18 @@
|
|||
//! can be socket-activated and not conflict with anything else running as a status notifier
|
||||
//! watcher.
|
||||
//!
|
||||
//! The daemon runs as long as as there is at least one client still connected. Which it checks
|
||||
//! for every `REFRESH_INTERVAL`.
|
||||
//! The daemon runs as long as there is at least one client still connected.
|
||||
|
||||
use crate::subscriptions::status_notifier_watcher::server::create_service;
|
||||
use crate::unique_names::UniqueNames;
|
||||
|
||||
use futures::StreamExt;
|
||||
use std::{collections::HashSet, time::Duration};
|
||||
use std::collections::HashSet;
|
||||
use zbus::fdo;
|
||||
use zbus::message::Header;
|
||||
|
||||
const DBUS_NAME: &str = "com.system76.CosmicStatusNotifierWatcher";
|
||||
const OBJECT_PATH: &str = "/CosmicStatusNotifierWatcher";
|
||||
const REFRESH_INTERVAL: Duration = Duration::from_secs(60);
|
||||
|
||||
/// Run daemon
|
||||
pub fn run() -> cosmic::iced::Result {
|
||||
if let Err(err) = run_inner() {
|
||||
|
|
@ -42,7 +39,7 @@ pub async fn cosmic_register(conn: &zbus::Connection) -> zbus::Result<()> {
|
|||
tokio::spawn(async move {
|
||||
while let Some(value) = stream.next().await {
|
||||
if let Some(_unique_name) = value {
|
||||
/// Register with new owner
|
||||
// Register with new owner.
|
||||
let _ = cosmic_watcher.register_applet().await;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::hash::Hash;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use cosmic::iced::{self, Subscription};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
|
|
@ -27,7 +28,7 @@ pub struct Icon {
|
|||
pub struct IconUpdate {
|
||||
pub name: Option<String>,
|
||||
pub pixmap: Option<Vec<Icon>>,
|
||||
// pub theme_path: Option<PathBuf>,
|
||||
pub theme_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl StatusNotifierItem {
|
||||
|
|
@ -118,11 +119,11 @@ impl StatusNotifierItem {
|
|||
async fn icon_events(item_proxy: StatusNotifierItemProxy<'static>) -> IconUpdate {
|
||||
let icon_name = item_proxy.icon_name().await;
|
||||
let icon_pixmap = item_proxy.icon_pixmap().await;
|
||||
// let icon_theme_path = item_proxy.icon_theme_path().await.map(PathBuf::from);
|
||||
let icon_theme_path = item_proxy.icon_theme_path().await.map(PathBuf::from);
|
||||
IconUpdate {
|
||||
name: icon_name.ok(),
|
||||
pixmap: icon_pixmap.ok(),
|
||||
// theme_path: icon_theme_path.ok().filter(|x| !x.as_os_str().is_empty()),
|
||||
theme_path: icon_theme_path.ok().filter(|x| !x.as_os_str().is_empty()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ edition = "2024"
|
|||
license = "GPL-3.0-only"
|
||||
|
||||
[dependencies]
|
||||
libcosmic.workspace = true
|
||||
cosmic.workspace = true
|
||||
anyhow.workspace = true
|
||||
cctk.workspace = true
|
||||
cosmic-comp-config = { git = "https://github.com/pop-os/cosmic-comp.git", rev = "5eb5af4" }
|
||||
cosmic-comp-config.workspace = true
|
||||
cosmic-protocols.workspace = true
|
||||
i18n-embed-fl.workspace = true
|
||||
i18n-embed.workspace = true
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ tile-current = Jelenlegi munkaterület csempézése
|
|||
shortcuts = Gyorsbillentyűk
|
||||
navigate-windows = Ablakok navigálása
|
||||
move-window = Ablak mozgatása
|
||||
toggle-floating-window = Az ablakok lebegtetésének be- és kikapcsolása
|
||||
toggle-floating-window = Lebegő ablak be/ki
|
||||
view-all-shortcuts = Az összes gyorsbillentyű megtekintése…
|
||||
active-hint = Aktív ablak kiemelése
|
||||
gaps = Hézagok
|
||||
floating-window-exceptions = Lebegő ablak kivételek…
|
||||
floating-window-exceptions = Lebegőablak-kivételek…
|
||||
window-management-settings = Ablakkezelési beállítások…
|
||||
all-workspaces = Összes munkaterület
|
||||
per-workspace = Munkaterületenként
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ use cosmic::{
|
|||
};
|
||||
use cosmic_comp_config::{CosmicCompConfig, TileBehavior};
|
||||
use cosmic_protocols::workspace::v2::client::zcosmic_workspace_handle_v2::TilingState;
|
||||
use std::{thread, time::Instant};
|
||||
use std::thread;
|
||||
use tracing::error;
|
||||
|
||||
const ID: &str = "com.system76.CosmicAppletTiling";
|
||||
|
|
@ -46,6 +46,7 @@ pub struct Window {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Message {
|
||||
TogglePopup,
|
||||
PopupClosed(Id),
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ cosmic-applets-config.workspace = true
|
|||
jiff = "0.2"
|
||||
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,5 +17,5 @@ tracing-subscriber.workspace = true
|
|||
tracing.workspace = true
|
||||
icu = { version = "2.1.1", features = ["compiled_data"] }
|
||||
zbus.workspace = true
|
||||
timedate-zbus = { git = "https://github.com/pop-os/dbus-settings-bindings" }
|
||||
timedate-zbus = { path = "../../dbus-settings-bindings/timedate" }
|
||||
logind-zbus = "5.3.2"
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ pub struct Window {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Message {
|
||||
TogglePopup,
|
||||
CloseRequested(window::Id),
|
||||
|
|
@ -361,7 +362,7 @@ impl cosmic::Application for Window {
|
|||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
fn time_subscription(mut show_seconds: watch::Receiver<bool>) -> Subscription<Message> {
|
||||
fn time_subscription(show_seconds: watch::Receiver<bool>) -> Subscription<Message> {
|
||||
struct Wrapper {
|
||||
inner: watch::Receiver<bool>,
|
||||
id: &'static str,
|
||||
|
|
@ -376,7 +377,7 @@ impl cosmic::Application for Window {
|
|||
inner: show_seconds,
|
||||
id: "time-sub",
|
||||
},
|
||||
|Wrapper { inner, id }| {
|
||||
|Wrapper { inner, id: _ }| {
|
||||
let mut show_seconds = inner.clone();
|
||||
stream::channel(1, move |mut output: mpsc::Sender<Message>| async move {
|
||||
// Mark this receiver's state as changed so that it always receives an initial
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ edition = "2024"
|
|||
license = "GPL-3.0-only"
|
||||
|
||||
[dependencies]
|
||||
libcosmic.workspace = true
|
||||
cosmic.workspace = true
|
||||
cctk.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ impl IcedWorkspacesApplet {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
enum Message {
|
||||
WorkspaceUpdate(WorkspacesUpdate),
|
||||
WorkspacePressed(ExtWorkspaceHandleV1),
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ cosmic-applet-time = { path = "../cosmic-applet-time" }
|
|||
cosmic-applet-workspaces = { path = "../cosmic-applet-workspaces" }
|
||||
cosmic-applet-input-sources = { path = "../cosmic-applet-input-sources" }
|
||||
cosmic-panel-button = { path = "../cosmic-panel-button" }
|
||||
libcosmic.workspace = true
|
||||
cosmic.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
tracing-log.workspace = true
|
||||
|
|
|
|||
|
|
@ -12,26 +12,44 @@ fn main() -> cosmic::iced::Result {
|
|||
};
|
||||
|
||||
let start = applet.rfind('/').map_or(0, |v| v + 1);
|
||||
let cmd = &applet.as_str()[start..];
|
||||
let cmd = applet.as_str()[start..].to_string();
|
||||
|
||||
tracing::info!("Starting `{cmd}` with version {VERSION}");
|
||||
|
||||
match cmd {
|
||||
"cosmic-app-list" => cosmic_app_list::run(),
|
||||
"cosmic-applet-a11y" => cosmic_applet_a11y::run(),
|
||||
"cosmic-applet-audio" => cosmic_applet_audio::run(),
|
||||
"cosmic-applet-battery" => cosmic_applet_battery::run(),
|
||||
"cosmic-applet-bluetooth" => cosmic_applet_bluetooth::run(),
|
||||
"cosmic-applet-minimize" => cosmic_applet_minimize::run(),
|
||||
"cosmic-applet-network" => cosmic_applet_network::run(),
|
||||
"cosmic-applet-notifications" => cosmic_applet_notifications::run(),
|
||||
"cosmic-applet-power" => cosmic_applet_power::run(),
|
||||
"cosmic-applet-status-area" => cosmic_applet_status_area::run(),
|
||||
"cosmic-applet-tiling" => cosmic_applet_tiling::run(),
|
||||
"cosmic-applet-time" => cosmic_applet_time::run(),
|
||||
"cosmic-applet-workspaces" => cosmic_applet_workspaces::run(),
|
||||
"cosmic-applet-input-sources" => cosmic_applet_input_sources::run(),
|
||||
"cosmic-panel-button" => cosmic_panel_button::run(),
|
||||
_ => Ok(()),
|
||||
let cmd_for_run = cmd.clone();
|
||||
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
|
||||
match cmd_for_run.as_str() {
|
||||
"cosmic-app-list" => cosmic_app_list::run(),
|
||||
"cosmic-applet-a11y" => cosmic_applet_a11y::run(),
|
||||
"cosmic-applet-audio" => cosmic_applet_audio::run(),
|
||||
"cosmic-applet-battery" => cosmic_applet_battery::run(),
|
||||
"cosmic-applet-bluetooth" => cosmic_applet_bluetooth::run(),
|
||||
"cosmic-applet-minimize" => cosmic_applet_minimize::run(),
|
||||
"cosmic-applet-network" => cosmic_applet_network::run(),
|
||||
"cosmic-applet-notifications" => cosmic_applet_notifications::run(),
|
||||
"cosmic-applet-power" => cosmic_applet_power::run(),
|
||||
"cosmic-applet-status-area" => cosmic_applet_status_area::run(),
|
||||
"cosmic-applet-tiling" => cosmic_applet_tiling::run(),
|
||||
"cosmic-applet-time" => cosmic_applet_time::run(),
|
||||
"cosmic-applet-workspaces" => cosmic_applet_workspaces::run(),
|
||||
"cosmic-applet-input-sources" => cosmic_applet_input_sources::run(),
|
||||
"cosmic-panel-button" => cosmic_panel_button::run(),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}));
|
||||
|
||||
match result {
|
||||
Ok(r) => r,
|
||||
Err(payload) => {
|
||||
let msg = payload
|
||||
.downcast_ref::<&str>()
|
||||
.map(|s| s.to_string())
|
||||
.or_else(|| payload.downcast_ref::<String>().cloned())
|
||||
.unwrap_or_else(|| "<non-string panic>".to_string());
|
||||
tracing::error!(
|
||||
"`{cmd}` panicked (likely compositor disconnect), exiting cleanly: {msg}"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ edition = "2024"
|
|||
license = "GPL-3.0-only"
|
||||
|
||||
[dependencies]
|
||||
libcosmic.workspace = true
|
||||
cosmic.workspace = true
|
||||
rustc-hash.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ impl Button {
|
|||
icon: cosmic::widget::icon::Handle,
|
||||
) -> cosmic::widget::Button<'a, Message> {
|
||||
let theme = cosmic::theme::active();
|
||||
let theme = theme.cosmic();
|
||||
let _theme = theme.cosmic();
|
||||
|
||||
let suggested = self.core.applet.suggested_size(icon.symbolic);
|
||||
let (major_padding, applet_padding_minor_axis) =
|
||||
|
|
@ -66,7 +66,7 @@ impl Button {
|
|||
.class(if symbolic {
|
||||
cosmic::theme::Svg::Custom(std::rc::Rc::new(|theme| {
|
||||
cosmic::iced::widget::svg::Style {
|
||||
color: Some(theme.cosmic().background.on.into()),
|
||||
color: Some(theme.cosmic().background(false).on.into()),
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ cosmic-applet-power-comment = Képernyő zárolása, kijelentkezés, felfüggesz
|
|||
cosmic-app-list-comment = Rögzített alkalmazások indítása és a megnyitott ablakok kezelése
|
||||
cosmic-applet-status-area-comment = Alkalmazásjelzők, melyek menüt is megjeleníthetnek a panelen
|
||||
cosmic-applet-status-area-keywords = COSMIC;kisalkalmazás;alkalmazás;app;jelző;értesítések;tálca;állapot;
|
||||
cosmic-applet-tiling-comment = Az aktív ablak kiemelésének, valamint az aktuális és munkaterületenkénti automatikus csempézés kezelése
|
||||
cosmic-applet-tiling-comment = Az aktív ablakkiemelés, valamint a jelenlegi és munkaterületenkénti automatikus csempézés kezelése
|
||||
cosmic-applet-tiling-keywords = COSMIC;kisalkalmazás;csempézés;kiemelés;munkaterületek;
|
||||
cosmic-applet-time-comment = Az aktuális idő megjelenítése a panelen, felugró naptárral
|
||||
cosmic-applet-time-keywords = COSMIC;kisalkalmazás;dátum;idő;naptár;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ cosmic-applet-minimize-comment = Керування згорнутими вік
|
|||
cosmic-applet-minimize-keywords = COSMIC;КОСМІК;КОСМОС;КОСМІЧНЕ;Віджет;Віджети;Згорнути;Згорнуті;Вікна;Вікно;
|
||||
cosmic-applet-network-comment = Керування мережевими з’єднаннями
|
||||
cosmic-applet-network-keywords = COSMIC;КОСМІК;КОСМОС;КОСМІЧНЕ;Віджет;Віджети;Мережа;Інтернет;
|
||||
cosmic-applet-notifications-comment = Керування сповіщеннями та режимом «Не турбувати»
|
||||
cosmic-applet-notifications-comment = Керування сповіщеннями та режимом Не турбувати
|
||||
cosmic-applet-notifications-keywords = COSMIC;КОСМІК;КОСМОС;КОСМІЧНЕ;Віджет;Віджети;Сповіщення;
|
||||
cosmic-applet-power-comment = Блокування екрана, вихід із сеансу, призупинення, перезапуск і вимкнення
|
||||
cosmic-applet-power-keywords = COSMIC;КОСМІК;КОСМОС;КОСМІЧНЕ;Віджет;Віджети;Сеанс;Користувач;Блокування;Вихід;Перезапуск;Вимкнення;Призупинення;Сон;
|
||||
|
|
|
|||
56
redeploy.sh
Executable file
56
redeploy.sh
Executable file
|
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env bash
|
||||
# Recompile et déploie le fork local cosmic-applets (multiplexor cosmic-applets +
|
||||
# binaire séparé cosmic-app-list).
|
||||
#
|
||||
# Cibles : /usr/local/bin/cosmic-applets et /usr/local/bin/cosmic-app-list
|
||||
# (binaires compilés manuellement, hors pacman, qui priment sur ceux du paquet
|
||||
# via $PATH /usr/local/bin avant /usr/bin).
|
||||
#
|
||||
# Branche de déploiement : yoda-dock-magnification (contient les commits
|
||||
# magnification + le fix Wayland error handling).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO="/home/lionel/Projets/COSMIC/cosmic-applets"
|
||||
TARGETS=(
|
||||
"cosmic-applets"
|
||||
"cosmic-app-list"
|
||||
"cosmic-panel-button"
|
||||
)
|
||||
BIN_DIR="/usr/local/bin"
|
||||
|
||||
cd "$REPO"
|
||||
|
||||
echo "==> Branche actuelle : $(git branch --show-current)"
|
||||
echo "==> Build release (workspace)..."
|
||||
cargo build --release --workspace
|
||||
|
||||
STAMP=$(date +%Y%m%d-%H%M%S)
|
||||
for bin in "${TARGETS[@]}"; do
|
||||
src="$REPO/target/release/$bin"
|
||||
dst="$BIN_DIR/$bin"
|
||||
|
||||
if [[ ! -f "$src" ]]; then
|
||||
echo "!! Binaire absent après build : $src" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -f "$dst" ]]; then
|
||||
echo "==> Backup $dst"
|
||||
sudo cp -a "$dst" "${dst}.bak.${STAMP}"
|
||||
fi
|
||||
|
||||
echo "==> Install $src -> $dst"
|
||||
sudo install -m755 "$src" "$dst"
|
||||
done
|
||||
|
||||
echo "==> Kill des process applet en cours (cosmic-panel relancera à la demande)"
|
||||
pkill -u "$USER" -f "/usr/local/bin/cosmic-applet" || true
|
||||
pkill -u "$USER" -f "/usr/local/bin/cosmic-app-list" || true
|
||||
pkill -u "$USER" -f "/usr/local/bin/cosmic-panel-button" || true
|
||||
|
||||
echo "==> Vérif"
|
||||
for bin in "${TARGETS[@]}"; do
|
||||
file "$BIN_DIR/$bin"
|
||||
done
|
||||
echo "OK — relogin recommandé pour repartir sur des process applet propres."
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "1.93"
|
||||
channel = "1.93.0"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue