chore: update dependencies

This commit is contained in:
Vukašin Vojinović 2026-04-03 00:09:03 +02:00 committed by Michael Murphy
parent a8d9416754
commit 2aa2c841fa
15 changed files with 1061 additions and 1159 deletions

15
.zed/settings.json Normal file
View file

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

1812
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -2,31 +2,32 @@
name = "cosmic-player"
version = "1.0.11"
edition = "2024"
rust-version = "1.93"
[build-dependencies]
vergen = { version = "8", features = ["git", "gitcl"] }
[dependencies]
ashpd = { version = "0.12", optional = true }
gstreamer-tag = "0.23"
image = "0.24.9"
gstreamer-tag = "0.25"
image = "0.25"
serde = { version = "1", features = ["serde_derive"] }
tempfile = "3"
tokio = { version = "1", features = ["sync"] }
url = { version = "2", features = ["serde"] }
# CLI arguments
clap_lex = "0.7"
clap_lex = "1.1"
# Internationalization
icu_collator = "1.5"
icu_provider = { version = "1.5", features = ["sync"] }
i18n-embed = { version = "0.14", features = [
icu_collator = "2.2"
icu_locale = "2.2"
i18n-embed = { version = "0.16", features = [
"fluent-system",
"desktop-requester",
] }
i18n-embed-fl = "0.7"
i18n-embed-fl = "0.10"
rust-embed = "8"
# Logging
env_logger = "0.10"
env_logger = "0.11"
log = "0.4"
[dependencies.iced_video_player]
@ -40,11 +41,12 @@ default-features = false
features = ["advanced-shaping", "tokio", "winit", "multi-window"]
[dependencies.mpris-server]
version = "0.8.1"
version = "0.10"
features = ["tokio"]
optional = true
[target.'cfg(unix)'.dependencies]
fork = "0.2"
fork = "0.7"
[features]
default = ["mpris-server", "xdg-portal", "wgpu", "wayland"]

View file

@ -1 +1 @@
reorder_imports = false
imports_granularity = "Module"

View file

@ -1,7 +1,8 @@
// Copyright 2024 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use std::{fs, io, path::PathBuf};
use std::path::PathBuf;
use std::{fs, io};
use clap_lex::RawArgs;
use log::warn;

View file

@ -1,11 +1,11 @@
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::{
cosmic_config::{self, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry},
theme,
};
use cosmic::cosmic_config::cosmic_config_derive::CosmicConfigEntry;
use cosmic::cosmic_config::{self, CosmicConfigEntry};
use cosmic::theme;
use serde::{Deserialize, Serialize};
use std::{collections::VecDeque, path::PathBuf};
use std::collections::VecDeque;
use std::path::PathBuf;
pub const CONFIG_VERSION: u64 = 1;

View file

@ -1,4 +1,5 @@
use cosmic::{iced::keyboard::Key, iced::core::keyboard::key::Named};
use cosmic::iced::keyboard::Key;
use cosmic::iced::keyboard::key::Named;
use std::collections::HashMap;
use crate::Action;

View file

@ -1,50 +1,64 @@
// SPDX-License-Identifier: GPL-3.0-only
use std::str::FromStr;
use std::sync::OnceLock;
use i18n_embed::{
DefaultLocalizer, LanguageLoader, Localizer,
fluent::{FluentLanguageLoader, fluent_language_loader},
};
use icu_collator::{Collator, CollatorOptions, Numeric};
use icu_provider::DataLocale;
use i18n_embed::fluent::{FluentLanguageLoader, fluent_language_loader};
use i18n_embed::{DefaultLocalizer, LanguageLoader, Localizer};
use icu_collator::options::CollatorOptions;
use icu_collator::preferences::CollationNumericOrdering;
use icu_collator::{Collator, CollatorBorrowed, CollatorPreferences};
use icu_locale::Locale;
use rust_embed::RustEmbed;
use std::sync::LazyLock;
#[derive(RustEmbed)]
#[folder = "i18n/"]
struct Localizations;
pub static LANGUAGE_LOADER: OnceLock<FluentLanguageLoader> = OnceLock::new();
pub static LANGUAGE_SORTER: OnceLock<Collator> = OnceLock::new();
pub static LANGUAGE_LOADER: LazyLock<FluentLanguageLoader> = LazyLock::new(|| {
let loader: FluentLanguageLoader = fluent_language_loader!();
loader
.load_fallback_language(&Localizations)
.expect("Error while loading fallback language");
loader
});
pub static LANGUAGE_SORTER: LazyLock<CollatorBorrowed> = LazyLock::new(|| {
let create_collator = |locale: Locale| {
let mut prefs = CollatorPreferences::from(locale);
prefs.numeric_ordering = Some(CollationNumericOrdering::True);
Collator::try_new(prefs, CollatorOptions::default()).ok()
};
Locale::try_from_str(&LANGUAGE_LOADER.current_language().to_string())
.ok()
.and_then(create_collator)
.or_else(|| {
Locale::try_from_str(&LANGUAGE_LOADER.fallback_language().to_string())
.ok()
.and_then(create_collator)
})
.unwrap_or_else(|| {
let locale = Locale::try_from_str("en-US").expect("en-US is a valid BCP-47 tag");
create_collator(locale)
.expect("Creating a collator from the system's current language, the fallback language, or American English should succeed")
})
});
#[macro_export]
macro_rules! fl {
($message_id:literal) => {{
i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER.get().unwrap(), $message_id)
i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id)
}};
($message_id:literal, $($args:expr),*) => {{
i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER.get().unwrap(), $message_id, $($args), *)
i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id, $($args), *)
}};
}
// Get the `Localizer` to be used for localizing this library.
pub fn localizer() -> Box<dyn Localizer> {
LANGUAGE_LOADER.get_or_init(|| {
let loader: FluentLanguageLoader = fluent_language_loader!();
loader
.load_fallback_language(&Localizations)
.expect("Error while loading fallback language");
loader
});
Box::from(DefaultLocalizer::new(
LANGUAGE_LOADER.get().unwrap(),
&Localizations,
))
Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations))
}
pub fn localize() {
@ -52,25 +66,6 @@ pub fn localize() {
let requested_languages = i18n_embed::DesktopLanguageRequester::requested_languages();
if let Err(error) = localizer.select(&requested_languages) {
eprintln!("Error while loading language for App List {}", error);
eprintln!("Error while loading language for COSMIC Media Player {error}",);
}
}
pub fn sorter() -> &'static Collator {
LANGUAGE_SORTER.get_or_init(|| {
let mut options = CollatorOptions::new();
options.numeric = Some(Numeric::On);
let localizer = localizer();
let language_loader = localizer.language_loader();
DataLocale::from_str(&language_loader.current_language().to_string())
.or_else(|_| DataLocale::from_str(&language_loader.fallback_language().to_string()))
.ok()
.and_then(|locale| Collator::try_new(&locale, options).ok())
.or_else(|| {
let locale = DataLocale::from_str("en-US").expect("en-US is a valid BCP-47 tag");
Collator::try_new(&locale, options).ok()
})
.expect("Creating a collator from the system's current language, the fallback language, or American English should succeed")
})
}

View file

@ -1,49 +1,32 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::{
action,
app::{Core, Settings, Task},
command::set_theme,
cosmic_config::{self, CosmicConfigEntry},
cosmic_theme, executor, font,
iced::{
event::{self, Event},
keyboard::{Event as KeyEvent, Key, Modifiers},
mouse::{Event as MouseEvent, ScrollDelta},
window::{self, set_mode},
Alignment, Background, Border, Color, ContentFit, Length, Limits, Subscription,
},
theme,
widget::{self, menu::action::MenuAction, nav_bar, segmented_button, Slider},
Application, ApplicationExt, Element,
};
use iced_video_player::{
gst::{self, prelude::*},
gst_pbutils, Video, VideoPlayer,
};
use std::{
any::TypeId,
collections::HashMap,
ffi::{CStr, CString},
fs,
path::{Path, PathBuf},
process,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
thread,
time::{Duration, Instant},
use cosmic::app::{Core, Settings, Task};
use cosmic::command::set_theme;
use cosmic::cosmic_config::{self, CosmicConfigEntry};
use cosmic::iced::event::{self, Event};
use cosmic::iced::keyboard::{Event as KeyEvent, Key, Modifiers};
use cosmic::iced::mouse::{Event as MouseEvent, ScrollDelta};
use cosmic::iced::window::{self, set_mode};
use cosmic::iced::{
Alignment, Background, Border, Color, ContentFit, Length, Limits, Subscription,
};
use cosmic::widget::menu::action::MenuAction;
use cosmic::widget::{self, Slider, nav_bar, segmented_button};
use cosmic::{Application, ApplicationExt, Element, action, cosmic_theme, executor, font, theme};
use iced_video_player::gst::prelude::*;
use iced_video_player::{Video, VideoPlayer, gst, gst_pbutils};
use std::any::TypeId;
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use std::{fs, process, thread};
use tokio::sync::mpsc;
use crate::{
config::{Config, ConfigState, CONFIG_VERSION, RepeatState},
key_bind::{key_binds, KeyBind},
mpris::subscription,
project::ProjectNode,
};
use crate::config::{CONFIG_VERSION, Config, ConfigState, RepeatState};
use crate::key_bind::{KeyBind, key_binds};
use crate::project::ProjectNode;
mod argparse;
mod config;
@ -611,10 +594,10 @@ impl App {
}
fn save_config_state(&mut self) {
if let Some(ref config_state_handler) = self.flags.config_state_handler {
if let Err(err) = self.flags.config_state.write_entry(config_state_handler) {
log::error!("failed to save config_state: {}", err);
}
if let Some(ref config_state_handler) = self.flags.config_state_handler
&& let Err(err) = self.flags.config_state.write_entry(config_state_handler)
{
log::error!("failed to save config_state: {}", err);
}
}
@ -623,9 +606,9 @@ impl App {
|| !self
.video_opt
.as_ref()
.map_or(false, |video| video.has_video())
.is_some_and(|video| video.has_video())
{
self.core.window.show_headerbar = true && !self.fullscreen;
self.core.window.show_headerbar = !self.fullscreen;
self.controls = true;
self.controls_time = Instant::now();
} else if self.controls && self.controls_time.elapsed() > CONTROLS_TIMEOUT {
@ -679,7 +662,7 @@ impl App {
..Default::default()
};
//TODO: use any other stream tags?
if let Some(tags) = self.audio_tags.get(0) {
if let Some(tags) = self.audio_tags.first() {
log::info!("{:#?}", tags);
if let Some(tag) = tags.get::<gst::tags::Album>() {
new.album = tag.get().into();
@ -752,11 +735,11 @@ impl App {
new.album_art_opt = url::Url::from_file_path(album_art.path()).ok();
}
}
if let Some((old, _, tx)) = &mut self.mpris_opt {
if new != *old {
*old = new.clone();
let _ = tx.send(MprisEvent::Meta(new.clone()));
}
if let Some((old, _, tx)) = &mut self.mpris_opt
&& new != *old
{
*old = new.clone();
let _ = tx.send(MprisEvent::Meta(new.clone()));
}
self.mpris_meta = new;
}
@ -951,7 +934,7 @@ impl Application for App {
fn on_escape(&mut self) -> Task<Self::Message> {
if self.fullscreen {
return self.update(Message::Fullscreen);
self.update(Message::Fullscreen)
} else {
Task::none()
}
@ -1210,12 +1193,12 @@ impl Application for App {
}
}
Message::AudioCode(code) => {
if let Ok(code) = i32::try_from(code) {
if let Some(video) = &self.video_opt {
let pipeline = video.pipeline();
pipeline.set_property("current-audio", code);
self.current_audio = pipeline.property("current-audio");
}
if let Ok(code) = i32::try_from(code)
&& let Some(video) = &self.video_opt
{
let pipeline = video.pipeline();
pipeline.set_property("current-audio", code);
self.current_audio = pipeline.property("current-audio");
}
}
Message::AudioToggle => {
@ -1225,12 +1208,12 @@ impl Application for App {
}
}
Message::AudioVolume(volume) => {
if let Some(video) = &mut self.video_opt {
if volume >= 0.0 && volume <= 1.0 {
video.set_volume(volume);
video.set_muted(false);
self.update_controls(true);
}
if let Some(video) = &mut self.video_opt
&& (0.0..=1.0).contains(&volume)
{
video.set_volume(volume);
video.set_muted(false);
self.update_controls(true);
}
}
Message::TextCode(index) => {
@ -1313,7 +1296,7 @@ impl Application for App {
}
}
if (volume >= 0.0 && volume <= 1.0) && !nav_bar_toggled {
if (0.0..=1.0).contains(&volume) && !nav_bar_toggled {
video.set_volume(volume);
video.set_muted(false);
self.update_controls(true);
@ -1537,20 +1520,20 @@ impl Application for App {
self.update_mpris_state();
}
Message::NewFrame => {
if let Some(video) = &mut self.video_opt {
if !self.dragging {
self.position = video.position().as_secs_f64();
if let Some(video) = &mut self.video_opt
&& !self.dragging
{
self.position = video.position().as_secs_f64();
if let Some((a, b)) = self.ab_repeat {
let target_a = a.unwrap_or(0.0);
let target_b = b.unwrap_or(self.duration);
if self.position >= target_b {
let _ = video.seek(Duration::from_secs_f64(target_a), true);
}
if let Some((a, b)) = self.ab_repeat {
let target_a = a.unwrap_or(0.0);
let target_b = b.unwrap_or(self.duration);
if self.position >= target_b {
let _ = video.seek(Duration::from_secs_f64(target_a), true);
}
self.update_controls(self.dropdown_opt.is_some());
}
self.update_controls(self.dropdown_opt.is_some());
}
}
Message::Reload => {
@ -1801,7 +1784,7 @@ impl Application for App {
.spacing(space_xxs)
.push(
widget::button::icon(
if self.video_opt.as_ref().map_or(true, |video| video.paused()) {
if self.video_opt.as_ref().is_none_or(|video| video.paused()) {
widget::icon::from_name("media-playback-start-symbolic").size(16)
} else {
widget::icon::from_name("media-playback-pause-symbolic").size(16)

View file

@ -1,14 +1,11 @@
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::{
theme,
widget::{
menu::{self, key_bind::KeyBind, ItemHeight, ItemWidth, MenuBar},
RcElementWrapper,
},
Element,
};
use std::{collections::HashMap, path::PathBuf};
use cosmic::widget::RcElementWrapper;
use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::menu::{self, ItemHeight, ItemWidth, MenuBar};
use cosmic::{Element, theme};
use std::collections::HashMap;
use std::path::PathBuf;
use crate::{Action, Config, ConfigState, Message, fl};
@ -22,10 +19,10 @@ pub fn menu_bar<'a>(
) -> Element<'a, Message> {
let home_dir_opt = std::env::home_dir();
let format_path = |path: &PathBuf| -> String {
if let Some(home_dir) = &home_dir_opt {
if let Ok(part) = path.strip_prefix(home_dir) {
return format!("~/{}", part.display());
}
if let Some(home_dir) = &home_dir_opt
&& let Ok(part) = path.strip_prefix(home_dir)
{
return format!("~/{}", part.display());
}
path.display().to_string()
};

View file

@ -1,18 +1,16 @@
use cosmic::{
iced::{
futures::{self, SinkExt, Stream},
Subscription, stream,
},
};
use cosmic::iced::futures::{self, SinkExt, Stream};
use cosmic::iced::{Subscription, stream};
use mpris_server::zbus::{Result, fdo};
use mpris_server::{
LoopStatus, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Property, RootInterface,
Server, Signal, Time, TrackId, Volume,
zbus::{Result, fdo},
};
use std::{any::TypeId, future, process};
use std::any::TypeId;
use std::{future, process};
use tokio::sync::{Mutex, mpsc};
use crate::{Message, MprisEvent, MprisMeta, MprisState, config::RepeatState};
use crate::config::RepeatState;
use crate::{Message, MprisEvent, MprisMeta, MprisState};
impl MprisMeta {
fn metadata(&self) -> Metadata {

View file

@ -1,11 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::widget;
use std::{
cmp::Ordering,
fs, io,
path::{Path, PathBuf},
};
use std::cmp::Ordering;
use std::path::{Path, PathBuf};
use std::{fs, io};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ProjectNode {
@ -26,15 +24,15 @@ impl ProjectNode {
let path = fs::canonicalize(path)?;
let name = path
.file_name()
.ok_or(io::Error::new(
io::ErrorKind::Other,
format!("path {:?} has no file name", path),
))?
.ok_or(io::Error::other(format!(
"path {:?} has no file name",
path
)))?
.to_str()
.ok_or(io::Error::new(
io::ErrorKind::Other,
format!("path {:?} is not valid UTF-8", path),
))?
.ok_or(io::Error::other(format!(
"path {:?} is not valid UTF-8",
path
)))?
.to_string();
Ok(if path.is_dir() {
Self::Folder {
@ -94,7 +92,7 @@ impl Ord for ProjectNode {
}
}
}
crate::localize::sorter().compare(self.name(), other.name())
crate::localize::LANGUAGE_SORTER.compare(self.name(), other.name())
}
}

View file

@ -1,6 +1,9 @@
use iced_video_player::Position;
use image::{DynamicImage, ImageFormat, RgbaImage};
use std::{error::Error, num::NonZero, path::Path, time::Duration};
use std::error::Error;
use std::num::NonZero;
use std::path::Path;
use std::time::Duration;
use url::Url;
use super::video;
@ -14,7 +17,7 @@ pub fn main(
let thumbnails = {
let mut video = match video::new_video(input, video::VideoSettings { mute: true }) {
Ok(ok) => ok,
Err(_err) => return Err(Into::into(format!("missing required plugin"))),
Err(_err) => return Err(Into::into("missing required plugin".to_string())),
};
let duration = video.duration();
@ -37,7 +40,7 @@ pub fn main(
pixels,
} => RgbaImage::from_raw(*width, *height, pixels.to_vec())
.map(DynamicImage::ImageRgba8)
.ok_or_else(|| format!("failed to convert thumbnail")),
.ok_or_else(|| "failed to convert thumbnail".to_string()),
_ => Err(format!("unsupported thumbnail handle {:?}", thumbnails[0])),
}
}?;

View file

@ -1,10 +1,8 @@
use iced_video_player::{
Video,
gst::{self, prelude::*},
gst_app, gst_pbutils,
};
use iced_video_player::gst::prelude::*;
use iced_video_player::{Video, gst, gst_app, gst_pbutils};
use cosmic::{action, app::Task};
use cosmic::action;
use cosmic::app::Task;
#[derive(Debug, Default)]
pub struct VideoSettings {
@ -33,13 +31,13 @@ pub fn new_video(
let Ok(elem) = vals[1].get::<gst::Element>() else {
return None;
};
if let Some(factory) = elem.factory() {
if factory.name() == "souphttpsrc" {
elem.set_property(
"user-agent",
"Mozilla/5.0 (X11; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0",
);
}
if let Some(factory) = elem.factory()
&& factory.name() == "souphttpsrc"
{
elem.set_property(
"user-agent",
"Mozilla/5.0 (X11; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0",
);
}
None
});
@ -65,18 +63,15 @@ pub fn new_video(
.unwrap()
.pop_filtered(&[gst::MessageType::Element])
{
match msg.view() {
gst::MessageView::Element(element) => {
if gst_pbutils::MissingPluginMessage::is(&element) {
commands.push(Task::perform(
async { action::app(super::Message::MissingPlugin(msg)) },
|x| x,
));
// Do one codec install at a time
break;
}
}
_ => {}
if let gst::MessageView::Element(element) = msg.view()
&& gst_pbutils::MissingPluginMessage::is(element)
{
commands.push(Task::perform(
async { action::app(super::Message::MissingPlugin(msg)) },
|x| x,
));
// Do one codec install at a time
break;
}
}
pipeline.set_state(gst::State::Null).unwrap();

View file

@ -3,10 +3,8 @@
//! Integrations with XDG portals.
use ashpd::{
desktop::inhibit::{InhibitFlags, InhibitProxy},
enumflags2::{BitFlags, make_bitflags},
};
use ashpd::desktop::inhibit::{InhibitFlags, InhibitProxy};
use ashpd::enumflags2::{BitFlags, make_bitflags};
use log::{debug, warn};
use tokio::sync::watch::Receiver;