chore: update dependencies
This commit is contained in:
parent
a8d9416754
commit
2aa2c841fa
15 changed files with 1061 additions and 1159 deletions
15
.zed/settings.json
Normal file
15
.zed/settings.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"format_on_save": "on",
|
||||||
|
"lsp": {
|
||||||
|
"rust-analyzer": {
|
||||||
|
"initialization_options": {
|
||||||
|
"check": {
|
||||||
|
"command": "clippy",
|
||||||
|
},
|
||||||
|
"rustfmt": {
|
||||||
|
"extraArgs": ["+nightly"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
1812
Cargo.lock
generated
1812
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
22
Cargo.toml
22
Cargo.toml
|
|
@ -2,31 +2,32 @@
|
||||||
name = "cosmic-player"
|
name = "cosmic-player"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
rust-version = "1.93"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
vergen = { version = "8", features = ["git", "gitcl"] }
|
vergen = { version = "8", features = ["git", "gitcl"] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ashpd = { version = "0.12", optional = true }
|
ashpd = { version = "0.12", optional = true }
|
||||||
gstreamer-tag = "0.23"
|
gstreamer-tag = "0.25"
|
||||||
image = "0.24.9"
|
image = "0.25"
|
||||||
serde = { version = "1", features = ["serde_derive"] }
|
serde = { version = "1", features = ["serde_derive"] }
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
tokio = { version = "1", features = ["sync"] }
|
tokio = { version = "1", features = ["sync"] }
|
||||||
url = { version = "2", features = ["serde"] }
|
url = { version = "2", features = ["serde"] }
|
||||||
# CLI arguments
|
# CLI arguments
|
||||||
clap_lex = "0.7"
|
clap_lex = "1.1"
|
||||||
# Internationalization
|
# Internationalization
|
||||||
icu_collator = "1.5"
|
icu_collator = "2.2"
|
||||||
icu_provider = { version = "1.5", features = ["sync"] }
|
icu_locale = "2.2"
|
||||||
i18n-embed = { version = "0.14", features = [
|
i18n-embed = { version = "0.16", features = [
|
||||||
"fluent-system",
|
"fluent-system",
|
||||||
"desktop-requester",
|
"desktop-requester",
|
||||||
] }
|
] }
|
||||||
i18n-embed-fl = "0.7"
|
i18n-embed-fl = "0.10"
|
||||||
rust-embed = "8"
|
rust-embed = "8"
|
||||||
# Logging
|
# Logging
|
||||||
env_logger = "0.10"
|
env_logger = "0.11"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
||||||
[dependencies.iced_video_player]
|
[dependencies.iced_video_player]
|
||||||
|
|
@ -40,11 +41,12 @@ default-features = false
|
||||||
features = ["advanced-shaping", "tokio", "winit", "multi-window"]
|
features = ["advanced-shaping", "tokio", "winit", "multi-window"]
|
||||||
|
|
||||||
[dependencies.mpris-server]
|
[dependencies.mpris-server]
|
||||||
version = "0.8.1"
|
version = "0.10"
|
||||||
|
features = ["tokio"]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
fork = "0.2"
|
fork = "0.7"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["mpris-server", "xdg-portal", "wgpu", "wayland"]
|
default = ["mpris-server", "xdg-portal", "wgpu", "wayland"]
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
reorder_imports = false
|
imports_granularity = "Module"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
// Copyright 2024 System76 <info@system76.com>
|
// Copyright 2024 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// 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 clap_lex::RawArgs;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::cosmic_config::cosmic_config_derive::CosmicConfigEntry;
|
||||||
cosmic_config::{self, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry},
|
use cosmic::cosmic_config::{self, CosmicConfigEntry};
|
||||||
theme,
|
use cosmic::theme;
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::VecDeque, path::PathBuf};
|
use std::collections::VecDeque;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub const CONFIG_VERSION: u64 = 1;
|
pub const CONFIG_VERSION: u64 = 1;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 std::collections::HashMap;
|
||||||
|
|
||||||
use crate::Action;
|
use crate::Action;
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,64 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use std::str::FromStr;
|
use i18n_embed::fluent::{FluentLanguageLoader, fluent_language_loader};
|
||||||
use std::sync::OnceLock;
|
use i18n_embed::{DefaultLocalizer, LanguageLoader, Localizer};
|
||||||
|
use icu_collator::options::CollatorOptions;
|
||||||
use i18n_embed::{
|
use icu_collator::preferences::CollationNumericOrdering;
|
||||||
DefaultLocalizer, LanguageLoader, Localizer,
|
use icu_collator::{Collator, CollatorBorrowed, CollatorPreferences};
|
||||||
fluent::{FluentLanguageLoader, fluent_language_loader},
|
use icu_locale::Locale;
|
||||||
};
|
|
||||||
use icu_collator::{Collator, CollatorOptions, Numeric};
|
|
||||||
use icu_provider::DataLocale;
|
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
#[derive(RustEmbed)]
|
#[derive(RustEmbed)]
|
||||||
#[folder = "i18n/"]
|
#[folder = "i18n/"]
|
||||||
struct Localizations;
|
struct Localizations;
|
||||||
|
|
||||||
pub static LANGUAGE_LOADER: OnceLock<FluentLanguageLoader> = OnceLock::new();
|
pub static LANGUAGE_LOADER: LazyLock<FluentLanguageLoader> = LazyLock::new(|| {
|
||||||
pub static LANGUAGE_SORTER: OnceLock<Collator> = OnceLock::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_export]
|
||||||
macro_rules! fl {
|
macro_rules! fl {
|
||||||
($message_id:literal) => {{
|
($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),*) => {{
|
($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.
|
// Get the `Localizer` to be used for localizing this library.
|
||||||
pub fn localizer() -> Box<dyn Localizer> {
|
pub fn localizer() -> Box<dyn Localizer> {
|
||||||
LANGUAGE_LOADER.get_or_init(|| {
|
Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations))
|
||||||
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,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn localize() {
|
pub fn localize() {
|
||||||
|
|
@ -52,25 +66,6 @@ pub fn localize() {
|
||||||
let requested_languages = i18n_embed::DesktopLanguageRequester::requested_languages();
|
let requested_languages = i18n_embed::DesktopLanguageRequester::requested_languages();
|
||||||
|
|
||||||
if let Err(error) = localizer.select(&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")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
||||||
139
src/main.rs
139
src/main.rs
|
|
@ -1,49 +1,32 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::app::{Core, Settings, Task};
|
||||||
action,
|
use cosmic::command::set_theme;
|
||||||
app::{Core, Settings, Task},
|
use cosmic::cosmic_config::{self, CosmicConfigEntry};
|
||||||
command::set_theme,
|
use cosmic::iced::event::{self, Event};
|
||||||
cosmic_config::{self, CosmicConfigEntry},
|
use cosmic::iced::keyboard::{Event as KeyEvent, Key, Modifiers};
|
||||||
cosmic_theme, executor, font,
|
use cosmic::iced::mouse::{Event as MouseEvent, ScrollDelta};
|
||||||
iced::{
|
use cosmic::iced::window::{self, set_mode};
|
||||||
event::{self, Event},
|
use cosmic::iced::{
|
||||||
keyboard::{Event as KeyEvent, Key, Modifiers},
|
Alignment, Background, Border, Color, ContentFit, Length, Limits, Subscription,
|
||||||
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::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 tokio::sync::mpsc;
|
||||||
|
|
||||||
use crate::{
|
use crate::config::{CONFIG_VERSION, Config, ConfigState, RepeatState};
|
||||||
config::{Config, ConfigState, CONFIG_VERSION, RepeatState},
|
use crate::key_bind::{KeyBind, key_binds};
|
||||||
key_bind::{key_binds, KeyBind},
|
use crate::project::ProjectNode;
|
||||||
mpris::subscription,
|
|
||||||
project::ProjectNode,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod argparse;
|
mod argparse;
|
||||||
mod config;
|
mod config;
|
||||||
|
|
@ -611,10 +594,10 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_config_state(&mut self) {
|
fn save_config_state(&mut self) {
|
||||||
if let Some(ref config_state_handler) = self.flags.config_state_handler {
|
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) {
|
&& let Err(err) = self.flags.config_state.write_entry(config_state_handler)
|
||||||
log::error!("failed to save config_state: {}", err);
|
{
|
||||||
}
|
log::error!("failed to save config_state: {}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -623,9 +606,9 @@ impl App {
|
||||||
|| !self
|
|| !self
|
||||||
.video_opt
|
.video_opt
|
||||||
.as_ref()
|
.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 = true;
|
||||||
self.controls_time = Instant::now();
|
self.controls_time = Instant::now();
|
||||||
} else if self.controls && self.controls_time.elapsed() > CONTROLS_TIMEOUT {
|
} else if self.controls && self.controls_time.elapsed() > CONTROLS_TIMEOUT {
|
||||||
|
|
@ -679,7 +662,7 @@ impl App {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
//TODO: use any other stream tags?
|
//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);
|
log::info!("{:#?}", tags);
|
||||||
if let Some(tag) = tags.get::<gst::tags::Album>() {
|
if let Some(tag) = tags.get::<gst::tags::Album>() {
|
||||||
new.album = tag.get().into();
|
new.album = tag.get().into();
|
||||||
|
|
@ -752,11 +735,11 @@ impl App {
|
||||||
new.album_art_opt = url::Url::from_file_path(album_art.path()).ok();
|
new.album_art_opt = url::Url::from_file_path(album_art.path()).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some((old, _, tx)) = &mut self.mpris_opt {
|
if let Some((old, _, tx)) = &mut self.mpris_opt
|
||||||
if new != *old {
|
&& new != *old
|
||||||
*old = new.clone();
|
{
|
||||||
let _ = tx.send(MprisEvent::Meta(new.clone()));
|
*old = new.clone();
|
||||||
}
|
let _ = tx.send(MprisEvent::Meta(new.clone()));
|
||||||
}
|
}
|
||||||
self.mpris_meta = new;
|
self.mpris_meta = new;
|
||||||
}
|
}
|
||||||
|
|
@ -951,7 +934,7 @@ impl Application for App {
|
||||||
|
|
||||||
fn on_escape(&mut self) -> Task<Self::Message> {
|
fn on_escape(&mut self) -> Task<Self::Message> {
|
||||||
if self.fullscreen {
|
if self.fullscreen {
|
||||||
return self.update(Message::Fullscreen);
|
self.update(Message::Fullscreen)
|
||||||
} else {
|
} else {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
@ -1210,12 +1193,12 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::AudioCode(code) => {
|
Message::AudioCode(code) => {
|
||||||
if let Ok(code) = i32::try_from(code) {
|
if let Ok(code) = i32::try_from(code)
|
||||||
if let Some(video) = &self.video_opt {
|
&& let Some(video) = &self.video_opt
|
||||||
let pipeline = video.pipeline();
|
{
|
||||||
pipeline.set_property("current-audio", code);
|
let pipeline = video.pipeline();
|
||||||
self.current_audio = pipeline.property("current-audio");
|
pipeline.set_property("current-audio", code);
|
||||||
}
|
self.current_audio = pipeline.property("current-audio");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::AudioToggle => {
|
Message::AudioToggle => {
|
||||||
|
|
@ -1225,12 +1208,12 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::AudioVolume(volume) => {
|
Message::AudioVolume(volume) => {
|
||||||
if let Some(video) = &mut self.video_opt {
|
if let Some(video) = &mut self.video_opt
|
||||||
if volume >= 0.0 && volume <= 1.0 {
|
&& (0.0..=1.0).contains(&volume)
|
||||||
video.set_volume(volume);
|
{
|
||||||
video.set_muted(false);
|
video.set_volume(volume);
|
||||||
self.update_controls(true);
|
video.set_muted(false);
|
||||||
}
|
self.update_controls(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::TextCode(index) => {
|
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_volume(volume);
|
||||||
video.set_muted(false);
|
video.set_muted(false);
|
||||||
self.update_controls(true);
|
self.update_controls(true);
|
||||||
|
|
@ -1537,20 +1520,20 @@ impl Application for App {
|
||||||
self.update_mpris_state();
|
self.update_mpris_state();
|
||||||
}
|
}
|
||||||
Message::NewFrame => {
|
Message::NewFrame => {
|
||||||
if let Some(video) = &mut self.video_opt {
|
if let Some(video) = &mut self.video_opt
|
||||||
if !self.dragging {
|
&& !self.dragging
|
||||||
self.position = video.position().as_secs_f64();
|
{
|
||||||
|
self.position = video.position().as_secs_f64();
|
||||||
|
|
||||||
if let Some((a, b)) = self.ab_repeat {
|
if let Some((a, b)) = self.ab_repeat {
|
||||||
let target_a = a.unwrap_or(0.0);
|
let target_a = a.unwrap_or(0.0);
|
||||||
let target_b = b.unwrap_or(self.duration);
|
let target_b = b.unwrap_or(self.duration);
|
||||||
if self.position >= target_b {
|
if self.position >= target_b {
|
||||||
let _ = video.seek(Duration::from_secs_f64(target_a), true);
|
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 => {
|
Message::Reload => {
|
||||||
|
|
@ -1801,7 +1784,7 @@ impl Application for App {
|
||||||
.spacing(space_xxs)
|
.spacing(space_xxs)
|
||||||
.push(
|
.push(
|
||||||
widget::button::icon(
|
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)
|
widget::icon::from_name("media-playback-start-symbolic").size(16)
|
||||||
} else {
|
} else {
|
||||||
widget::icon::from_name("media-playback-pause-symbolic").size(16)
|
widget::icon::from_name("media-playback-pause-symbolic").size(16)
|
||||||
|
|
|
||||||
23
src/menu.rs
23
src/menu.rs
|
|
@ -1,14 +1,11 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::widget::RcElementWrapper;
|
||||||
theme,
|
use cosmic::widget::menu::key_bind::KeyBind;
|
||||||
widget::{
|
use cosmic::widget::menu::{self, ItemHeight, ItemWidth, MenuBar};
|
||||||
menu::{self, key_bind::KeyBind, ItemHeight, ItemWidth, MenuBar},
|
use cosmic::{Element, theme};
|
||||||
RcElementWrapper,
|
use std::collections::HashMap;
|
||||||
},
|
use std::path::PathBuf;
|
||||||
Element,
|
|
||||||
};
|
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
|
||||||
|
|
||||||
use crate::{Action, Config, ConfigState, Message, fl};
|
use crate::{Action, Config, ConfigState, Message, fl};
|
||||||
|
|
||||||
|
|
@ -22,10 +19,10 @@ pub fn menu_bar<'a>(
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
let home_dir_opt = std::env::home_dir();
|
let home_dir_opt = std::env::home_dir();
|
||||||
let format_path = |path: &PathBuf| -> String {
|
let format_path = |path: &PathBuf| -> String {
|
||||||
if let Some(home_dir) = &home_dir_opt {
|
if let Some(home_dir) = &home_dir_opt
|
||||||
if let Ok(part) = path.strip_prefix(home_dir) {
|
&& let Ok(part) = path.strip_prefix(home_dir)
|
||||||
return format!("~/{}", part.display());
|
{
|
||||||
}
|
return format!("~/{}", part.display());
|
||||||
}
|
}
|
||||||
path.display().to_string()
|
path.display().to_string()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
16
src/mpris.rs
16
src/mpris.rs
|
|
@ -1,18 +1,16 @@
|
||||||
use cosmic::{
|
use cosmic::iced::futures::{self, SinkExt, Stream};
|
||||||
iced::{
|
use cosmic::iced::{Subscription, stream};
|
||||||
futures::{self, SinkExt, Stream},
|
use mpris_server::zbus::{Result, fdo};
|
||||||
Subscription, stream,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use mpris_server::{
|
use mpris_server::{
|
||||||
LoopStatus, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Property, RootInterface,
|
LoopStatus, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Property, RootInterface,
|
||||||
Server, Signal, Time, TrackId, Volume,
|
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 tokio::sync::{Mutex, mpsc};
|
||||||
|
|
||||||
use crate::{Message, MprisEvent, MprisMeta, MprisState, config::RepeatState};
|
use crate::config::RepeatState;
|
||||||
|
use crate::{Message, MprisEvent, MprisMeta, MprisState};
|
||||||
|
|
||||||
impl MprisMeta {
|
impl MprisMeta {
|
||||||
fn metadata(&self) -> Metadata {
|
fn metadata(&self) -> Metadata {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use cosmic::widget;
|
use cosmic::widget;
|
||||||
use std::{
|
use std::cmp::Ordering;
|
||||||
cmp::Ordering,
|
use std::path::{Path, PathBuf};
|
||||||
fs, io,
|
use std::{fs, io};
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum ProjectNode {
|
pub enum ProjectNode {
|
||||||
|
|
@ -26,15 +24,15 @@ impl ProjectNode {
|
||||||
let path = fs::canonicalize(path)?;
|
let path = fs::canonicalize(path)?;
|
||||||
let name = path
|
let name = path
|
||||||
.file_name()
|
.file_name()
|
||||||
.ok_or(io::Error::new(
|
.ok_or(io::Error::other(format!(
|
||||||
io::ErrorKind::Other,
|
"path {:?} has no file name",
|
||||||
format!("path {:?} has no file name", path),
|
path
|
||||||
))?
|
)))?
|
||||||
.to_str()
|
.to_str()
|
||||||
.ok_or(io::Error::new(
|
.ok_or(io::Error::other(format!(
|
||||||
io::ErrorKind::Other,
|
"path {:?} is not valid UTF-8",
|
||||||
format!("path {:?} is not valid UTF-8", path),
|
path
|
||||||
))?
|
)))?
|
||||||
.to_string();
|
.to_string();
|
||||||
Ok(if path.is_dir() {
|
Ok(if path.is_dir() {
|
||||||
Self::Folder {
|
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
use iced_video_player::Position;
|
use iced_video_player::Position;
|
||||||
use image::{DynamicImage, ImageFormat, RgbaImage};
|
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 url::Url;
|
||||||
|
|
||||||
use super::video;
|
use super::video;
|
||||||
|
|
@ -14,7 +17,7 @@ pub fn main(
|
||||||
let thumbnails = {
|
let thumbnails = {
|
||||||
let mut video = match video::new_video(input, video::VideoSettings { mute: true }) {
|
let mut video = match video::new_video(input, video::VideoSettings { mute: true }) {
|
||||||
Ok(ok) => ok,
|
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();
|
let duration = video.duration();
|
||||||
|
|
@ -37,7 +40,7 @@ pub fn main(
|
||||||
pixels,
|
pixels,
|
||||||
} => RgbaImage::from_raw(*width, *height, pixels.to_vec())
|
} => RgbaImage::from_raw(*width, *height, pixels.to_vec())
|
||||||
.map(DynamicImage::ImageRgba8)
|
.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])),
|
_ => Err(format!("unsupported thumbnail handle {:?}", thumbnails[0])),
|
||||||
}
|
}
|
||||||
}?;
|
}?;
|
||||||
|
|
|
||||||
45
src/video.rs
45
src/video.rs
|
|
@ -1,10 +1,8 @@
|
||||||
use iced_video_player::{
|
use iced_video_player::gst::prelude::*;
|
||||||
Video,
|
use iced_video_player::{Video, gst, gst_app, gst_pbutils};
|
||||||
gst::{self, prelude::*},
|
|
||||||
gst_app, gst_pbutils,
|
|
||||||
};
|
|
||||||
|
|
||||||
use cosmic::{action, app::Task};
|
use cosmic::action;
|
||||||
|
use cosmic::app::Task;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct VideoSettings {
|
pub struct VideoSettings {
|
||||||
|
|
@ -33,13 +31,13 @@ pub fn new_video(
|
||||||
let Ok(elem) = vals[1].get::<gst::Element>() else {
|
let Ok(elem) = vals[1].get::<gst::Element>() else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
if let Some(factory) = elem.factory() {
|
if let Some(factory) = elem.factory()
|
||||||
if factory.name() == "souphttpsrc" {
|
&& factory.name() == "souphttpsrc"
|
||||||
elem.set_property(
|
{
|
||||||
"user-agent",
|
elem.set_property(
|
||||||
"Mozilla/5.0 (X11; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0",
|
"user-agent",
|
||||||
);
|
"Mozilla/5.0 (X11; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0",
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
});
|
});
|
||||||
|
|
@ -65,18 +63,15 @@ pub fn new_video(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.pop_filtered(&[gst::MessageType::Element])
|
.pop_filtered(&[gst::MessageType::Element])
|
||||||
{
|
{
|
||||||
match msg.view() {
|
if let gst::MessageView::Element(element) = msg.view()
|
||||||
gst::MessageView::Element(element) => {
|
&& gst_pbutils::MissingPluginMessage::is(element)
|
||||||
if gst_pbutils::MissingPluginMessage::is(&element) {
|
{
|
||||||
commands.push(Task::perform(
|
commands.push(Task::perform(
|
||||||
async { action::app(super::Message::MissingPlugin(msg)) },
|
async { action::app(super::Message::MissingPlugin(msg)) },
|
||||||
|x| x,
|
|x| x,
|
||||||
));
|
));
|
||||||
// Do one codec install at a time
|
// Do one codec install at a time
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pipeline.set_state(gst::State::Null).unwrap();
|
pipeline.set_state(gst::State::Null).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,8 @@
|
||||||
|
|
||||||
//! Integrations with XDG portals.
|
//! Integrations with XDG portals.
|
||||||
|
|
||||||
use ashpd::{
|
use ashpd::desktop::inhibit::{InhibitFlags, InhibitProxy};
|
||||||
desktop::inhibit::{InhibitFlags, InhibitProxy},
|
use ashpd::enumflags2::{BitFlags, make_bitflags};
|
||||||
enumflags2::{BitFlags, make_bitflags},
|
|
||||||
};
|
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use tokio::sync::watch::Receiver;
|
use tokio::sync::watch::Receiver;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue