// Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only use cosmic::{ app::{message, Command, Core, Settings}, cosmic_config::{self, CosmicConfigEntry}, cosmic_theme, executor, font, iced::{ event::{self, Event}, keyboard::{Event as KeyEvent, Key, Modifiers}, mouse::Event as MouseEvent, subscription::Subscription, window, Alignment, Background, Border, Color, Length, Limits, }, theme, widget::{self, menu::action::MenuAction, Slider}, Application, ApplicationExt, Element, }; use iced_video_player::{ gst::{self, prelude::*}, gst_app, gst_pbutils, Video, VideoPlayer, }; use std::{ any::TypeId, collections::HashMap, ffi::{CStr, CString}, fs, process, thread, time::{Duration, Instant}, }; use crate::{ config::{Config, CONFIG_VERSION}, key_bind::{key_binds, KeyBind}, }; mod config; mod key_bind; mod localize; mod menu; static CONTROLS_TIMEOUT: Duration = Duration::new(2, 0); const GST_PLAY_FLAG_VIDEO: i32 = 1 << 0; const GST_PLAY_FLAG_AUDIO: i32 = 1 << 1; const GST_PLAY_FLAG_TEXT: i32 = 1 << 2; fn language_name(code: &str) -> Option { let code_c = CString::new(code).ok()?; let name_c = unsafe { //TODO: export this in gstreamer_tag let name_ptr = gstreamer_tag::ffi::gst_tag_get_language_name(code_c.as_ptr()); if name_ptr.is_null() { return None; } CStr::from_ptr(name_ptr) }; let name = name_c.to_str().ok()?; Some(name.to_string()) } /// Runs application with these settings #[rustfmt::skip] pub fn main() -> Result<(), Box> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); localize::localize(); let (config_handler, config) = match cosmic_config::Config::new(App::APP_ID, CONFIG_VERSION) { Ok(config_handler) => { let config = match Config::get_entry(&config_handler) { Ok(ok) => ok, Err((errs, config)) => { log::info!("errors loading config: {:?}", errs); config } }; (Some(config_handler), config) } Err(err) => { log::error!("failed to create config handler: {}", err); (None, Config::default()) } }; let mut settings = Settings::default(); settings = settings.theme(config.app_theme.theme()); settings = settings.size_limits(Limits::NONE.min_width(360.0).min_height(180.0)); let url_opt = match std::env::args().nth(1) { Some(arg) => match url::Url::parse(&arg) { Ok(url) => Some(url), Err(_) => match fs::canonicalize(&arg) { Ok(path) => match url::Url::from_file_path(&path) { Ok(url) => Some(url), Err(()) => { log::warn!("failed to parse argument {:?}", arg); None } }, Err(_) => { log::warn!("failed to parse argument {:?}", arg); None } }, }, None => None, }; let flags = Flags { config_handler, config, url_opt, }; cosmic::app::run::(settings, flags)?; Ok(()) } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Action { FileClose, FileOpen, Fullscreen, PlayPause, SeekBackward, SeekForward, WindowClose, } impl MenuAction for Action { type Message = Message; fn message(&self) -> Message { match self { Self::FileClose => Message::FileClose, Self::FileOpen => Message::FileOpen, Self::Fullscreen => Message::Fullscreen, Self::PlayPause => Message::PlayPause, Self::SeekBackward => Message::SeekRelative(-10.0), Self::SeekForward => Message::SeekRelative(10.0), Self::WindowClose => Message::WindowClose, } } } #[derive(Clone)] pub struct Flags { config_handler: Option, config: Config, url_opt: Option, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum DropdownKind { Audio, Subtitle, } /// Messages that are used specifically by our [`App`]. #[derive(Clone, Debug)] pub enum Message { None, Config(Config), DropdownToggle(DropdownKind), FileClose, FileLoad(url::Url), FileOpen, Fullscreen, Key(Modifiers, Key), AudioCode(usize), AudioToggle, AudioVolume(f64), TextCode(usize), PlayPause, Seek(f64), SeekRelative(f64), SeekRelease, EndOfStream, MissingPlugin(gst::Message), NewFrame, Reload, ShowControls, SystemThemeModeChange(cosmic_theme::ThemeMode), WindowClose, } /// The [`App`] stores application-specific state. pub struct App { core: Core, flags: Flags, controls: bool, controls_time: Instant, dropdown_opt: Option, fullscreen: bool, key_binds: HashMap, video_opt: Option