diff --git a/Cargo.lock b/Cargo.lock index 53a9d26..189d31b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -235,9 +235,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" dependencies = [ "async-lock", "cfg-if", @@ -249,7 +249,7 @@ dependencies = [ "rustix", "slab", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -265,9 +265,9 @@ dependencies = [ [[package]] name = "async-process" -version = "2.2.3" +version = "2.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7eda79bbd84e29c2b308d1dc099d7de8dcc7035e48f4bf5dc4a531a44ff5e2a" +checksum = "a8a07789659a4d385b79b18b9127fc27e1a59e1e89117c78c5ea3b806f016374" dependencies = [ "async-channel", "async-io", @@ -280,7 +280,7 @@ dependencies = [ "futures-lite", "rustix", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -291,14 +291,14 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] [[package]] name = "async-signal" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ "async-io", "async-lock", @@ -309,7 +309,7 @@ dependencies = [ "rustix", "signal-hook-registry", "slab", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -326,7 +326,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] [[package]] @@ -480,7 +480,7 @@ checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] [[package]] @@ -549,9 +549,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.8" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549" +checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" dependencies = [ "jobserver", "libc", @@ -714,9 +714,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-graphics" @@ -951,7 +951,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] [[package]] @@ -1151,7 +1151,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] [[package]] @@ -1239,7 +1239,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] [[package]] @@ -1409,7 +1409,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] [[package]] @@ -1422,6 +1422,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "glow" version = "0.13.1" @@ -1671,9 +1677,8 @@ dependencies = [ [[package]] name = "iced_layershell" -version = "0.4.0-rc1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace5190b78ae4b839bcb4085aa054c44b7ac89ae80ffe86cc65768c25d12d68f" +version = "0.4.0-rc2" +source = "git+https://github.com/waycrate/exwlshelleventloop#f64677fd8d31103192b4ba696f14aeda447c005b" dependencies = [ "futures", "iced", @@ -1958,19 +1963,20 @@ dependencies = [ ] [[package]] -name = "lala_musicbar" +name = "lala-bar" version = "0.2.7" dependencies = [ "alsa", "anyhow", "env_logger", + "futures", "futures-util", "gio", "iced", "iced_futures", "iced_layershell", "iced_runtime", - "once_cell", + "notification_iced", "regex", "serde", "tokio", @@ -1983,9 +1989,8 @@ dependencies = [ [[package]] name = "layershellev" -version = "0.4.0-rc1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043232f79a51a9618163de3e658024992c6dab911ae71a269dc760a9da6ae205" +version = "0.4.0-rc2" +source = "git+https://github.com/waycrate/exwlshelleventloop#f64677fd8d31103192b4ba696f14aeda447c005b" dependencies = [ "bitflags 2.6.0", "log", @@ -2242,6 +2247,18 @@ dependencies = [ "memoffset", ] +[[package]] +name = "notification_iced" +version = "0.1.0" +dependencies = [ + "futures", + "futures-util", + "glob", + "serde", + "url", + "zbus", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2289,7 +2306,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] [[package]] @@ -2517,7 +2534,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] [[package]] @@ -2616,7 +2633,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] [[package]] @@ -2648,9 +2665,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", "fastrand", @@ -2678,9 +2695,9 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.2" +version = "3.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" dependencies = [ "cfg-if", "concurrent-queue", @@ -2688,7 +2705,7 @@ dependencies = [ "pin-project-lite", "rustix", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3053,22 +3070,22 @@ checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" [[package]] name = "serde" -version = "1.0.205" +version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150" +checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.205" +version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1" +checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] [[package]] @@ -3079,7 +3096,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] [[package]] @@ -3364,9 +3381,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "837a7e8026c6ce912ff01cefbe8cafc2f8010ac49682e2a3d9decc3bce1ecaaf" dependencies = [ "proc-macro2", "quote", @@ -3440,7 +3457,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] [[package]] @@ -3545,7 +3562,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] [[package]] @@ -3612,7 +3629,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] [[package]] @@ -3893,7 +3910,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", "wasm-bindgen-shared", ] @@ -3927,7 +3944,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3955,9 +3972,8 @@ dependencies = [ [[package]] name = "waycrate_xkbkeycode" -version = "0.4.0-rc1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4792f051d3b0772685750b424522cf2c4ad11aa5690e670eb2e42986c4163d6" +version = "0.4.0-rc2" +source = "git+https://github.com/waycrate/exwlshelleventloop#f64677fd8d31103192b4ba696f14aeda447c005b" dependencies = [ "bitflags 2.6.0", "log", @@ -4632,9 +4648,9 @@ checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" [[package]] name = "xcursor" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d491ee231a51ae64a5b762114c3ac2104b967aadba1de45c86ca42cf051513b7" +checksum = "f513f231f0810b04d988f0df4fb16ef0b6b25d23248f2c4b56b074e6b1b0ffe4" [[package]] name = "xdg" @@ -4644,12 +4660,12 @@ checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" [[package]] name = "xdg-home" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca91dcf8f93db085f3a0a29358cd0b9d670915468f4290e8b85d118a34211ab8" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4757,7 +4773,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", "zvariant_utils", ] @@ -4796,7 +4812,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] [[package]] @@ -4830,7 +4846,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", "zvariant_utils", ] @@ -4842,5 +4858,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.73", ] diff --git a/Cargo.toml b/Cargo.toml index f5f094f..0e20ec8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,36 +1,15 @@ -[package] -name = "lala_musicbar" -version = "0.2.7" -edition = "2021" +[workspace] +resolver = "2" +members = ["./lala_bar", "./notification_iced"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[workspace.package] +version = "0.1.0" -[dependencies] -iced = { version = "0.12", features = [ - "tokio", - "debug", - "image", - "advanced", - "svg", -] } -#iced_native = "0.12" -iced_runtime = "0.12" -iced_layershell = "0.4.0-rc1" -tokio = { version = "1.39", features = ["full"] } -iced_futures = "0.12.0" -env_logger = "0.11.5" -tracing = "0.1.40" -futures-util = "0.3.30" -once_cell = "1.19.0" +[workspace.dependencies] +notification_iced = { version = "0.1.0", path = "notification_iced"} +futures = "0.3.30" serde = { version = "1.0.204", features = ["derive"] } - -zbus = { version = "4.4.0", default-features = false, features = ["tokio"] } -tracing-subscriber = "0.3.18" -anyhow = "1.0.86" -alsa = "0.9.0" - -gio = "0.20.0" -regex = "1.10.5" -xdg = "2.5.2" url = "2.5.2" +zbus = { version = "4.4.0", default-features = false, features = ["tokio"] } +futures-util = "0.3.30" diff --git a/lala_bar/Cargo.toml b/lala_bar/Cargo.toml new file mode 100644 index 0000000..d0574f4 --- /dev/null +++ b/lala_bar/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "lala-bar" +version = "0.2.7" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +iced = { version = "0.12", features = [ + "tokio", + "debug", + "image", + "advanced", + "svg", +] } +#iced_native = "0.12" +iced_runtime = "0.12" +iced_layershell = { git = "https://github.com/waycrate/exwlshelleventloop", package = "iced_layershell" } +tokio = { version = "1.39", features = ["full"] } +iced_futures = "0.12.0" +env_logger = "0.11.5" +tracing = "0.1.40" + +futures-util.workspace = true +serde.workspace = true + +zbus.workspace = true +tracing-subscriber = "0.3.18" +anyhow = "1.0.86" +alsa = "0.9.0" + +gio = "0.20.0" +regex = "1.10.5" +xdg = "2.5.2" +url.workspace = true +futures.workspace = true +notification_iced.workspace = true diff --git a/src/aximer.rs b/lala_bar/src/aximer.rs similarity index 100% rename from src/aximer.rs rename to lala_bar/src/aximer.rs diff --git a/src/launcher.rs b/lala_bar/src/launcher.rs similarity index 89% rename from src/launcher.rs rename to lala_bar/src/launcher.rs index 8479809..cd8b320 100644 --- a/src/launcher.rs +++ b/lala_bar/src/launcher.rs @@ -20,6 +20,14 @@ pub struct Launcher { pub should_delete: bool, } +#[derive(Debug, Clone)] +pub enum LaunchMessage { + SearchEditChanged(String), + SearchSubmit, + Launch(usize), + IcedEvent(Event), +} + impl Launcher { pub fn new() -> Self { Self { @@ -34,11 +42,11 @@ impl Launcher { text_input::focus(INPUT_ID.clone()) } - pub fn update(&mut self, message: Message, id: iced::window::Id) -> Command { + pub fn update(&mut self, message: LaunchMessage, id: iced::window::Id) -> Command { use iced::keyboard::key::Named; use iced_runtime::keyboard; match message { - Message::SearchSubmit => { + LaunchMessage::SearchSubmit => { let re = regex::Regex::new(&self.text).ok(); let index = self .apps @@ -63,17 +71,17 @@ impl Launcher { Command::none() } } - Message::SearchEditChanged(edit) => { + LaunchMessage::SearchEditChanged(edit) => { self.scrollpos = 0; self.text = edit; Command::none() } - Message::Launch(index) => { + LaunchMessage::Launch(index) => { self.apps[index].launch(); self.should_delete = true; Command::single(Action::Window(WindowAction::Close(id))) } - Message::IcedEvent(event) => { + LaunchMessage::IcedEvent(event) => { let mut len = self.apps.len(); let re = regex::Regex::new(&self.text).ok(); @@ -112,7 +120,6 @@ impl Launcher { } text_input::focus(INPUT_ID.clone()) } - _ => Command::none(), } } @@ -120,8 +127,8 @@ impl Launcher { let re = regex::Regex::new(&self.text).ok(); let text_ip: Element = text_input("put the launcher name", &self.text) .padding(10) - .on_input(Message::SearchEditChanged) - .on_submit(Message::SearchSubmit) + .on_input(|msg| Message::LauncherInfo(LaunchMessage::SearchEditChanged(msg))) + .on_submit(Message::LauncherInfo(LaunchMessage::SearchSubmit)) .id(INPUT_ID.clone()) .into(); let bottom_vec: Vec> = self diff --git a/src/launcher/applications.rs b/lala_bar/src/launcher/applications.rs similarity index 96% rename from src/launcher/applications.rs rename to lala_bar/src/launcher/applications.rs index 04e9869..d10da50 100644 --- a/src/launcher/applications.rs +++ b/lala_bar/src/launcher/applications.rs @@ -1,6 +1,8 @@ use std::path::PathBuf; use std::str::FromStr; +use super::LaunchMessage; + use gio::{AppLaunchContext, DesktopAppInfo}; use gio::prelude::*; @@ -10,7 +12,7 @@ use iced::{Element, Length}; use super::Message; -static DEFAULT_ICON: &[u8] = include_bytes!("../../misc/text-plain.svg"); +static DEFAULT_ICON: &[u8] = include_bytes!("../../../misc/text-plain.svg"); #[allow(unused)] #[derive(Debug, Clone)] @@ -79,7 +81,7 @@ impl App { ] .spacing(10), ) - .on_press(Message::Launch(index)) + .on_press(Message::LauncherInfo(LaunchMessage::Launch(index))) .width(Length::Fill) .height(Length::Fixed(85.)) .style(if selected { diff --git a/lala_bar/src/main.rs b/lala_bar/src/main.rs new file mode 100644 index 0000000..2fd6d9d --- /dev/null +++ b/lala_bar/src/main.rs @@ -0,0 +1,1222 @@ +use std::collections::HashMap; + +use futures::future::pending; +use futures::StreamExt; +use iced::widget::{ + button, column, container, image, row, scrollable, slider, svg, text, text_input, Space, +}; +use iced::{executor, Font}; +use iced::{Command, Element, Length, Theme}; +use iced_layershell::actions::{ + LayershellCustomActionsWithIdAndInfo, LayershellCustomActionsWithInfo, +}; +use launcher::{LaunchMessage, Launcher}; +use notification_iced::{ + start_connection, ImageInfo, LaLaMako, NotifyMessage, NotifyUnit, VersionInfo, DEFAULT_ACTION, + NOTIFICATION_SERVICE_PATH, +}; +use zbus_mpirs::ServiceInfo; + +use iced_layershell::reexport::{Anchor, KeyboardInteractivity, Layer, NewLayerShellSettings}; +use iced_layershell::settings::{LayerShellSettings, Settings}; +use iced_layershell::MultiApplication; +use iced_runtime::command::Action; +use iced_runtime::window::Action as WindowAction; + +use futures::channel::mpsc::{channel, Receiver, Sender}; + +use tokio::sync::Mutex; + +use std::sync::Arc; + +mod aximer; +mod launcher; +mod zbus_mpirs; + +type LaLaShellIdAction = LayershellCustomActionsWithIdAndInfo; +type LalaShellAction = LayershellCustomActionsWithInfo; + +const BEGINNING_UP_MARGIN: i32 = 10; + +const UNIT_MARGIN: i32 = 135; + +const EXTRAINF_MARGIN: i32 = BEGINNING_UP_MARGIN + 4 * UNIT_MARGIN; + +const LAUNCHER_SVG: &[u8] = include_bytes!("../../misc/launcher.svg"); + +const RESET_SVG: &[u8] = include_bytes!("../../misc/reset.svg"); + +pub fn main() -> Result<(), iced_layershell::Error> { + env_logger::builder().format_timestamp(None).init(); + + LalaMusicBar::run(Settings { + layer_settings: LayerShellSettings { + size: Some((0, 35)), + exclusive_zone: 35, + anchor: Anchor::Bottom | Anchor::Left | Anchor::Right, + layer: Layer::Top, + ..Default::default() + }, + ..Default::default() + }) +} + +#[derive(Debug, Clone)] +enum LaLaInfo { + Launcher, + Notify(Box), + HiddenInfo, + RightPanel, +} + +#[derive(Debug, Clone)] +struct NotifyUnitWidgetInfo { + upper: i32, + counter: usize, + inline_reply: String, + unit: NotifyUnit, +} + +impl NotifyUnitWidgetInfo { + fn button<'a>(&self, id: Option, hidden: bool) -> Element<'a, Message> { + let notify = &self.unit; + let counter = self.counter; + match notify.image() { + Some(ImageInfo::Svg(path)) => button(row![ + svg(svg::Handle::from_path(path)).height(Length::Fill), + Space::with_width(4.), + column![ + text(notify.summery.clone()) + .shaping(text::Shaping::Advanced) + .size(20) + .font(Font { + weight: iced::font::Weight::Bold, + ..Default::default() + }), + text(notify.body.clone()).shaping(text::Shaping::Advanced) + ] + ]) + .style(iced::theme::Button::Secondary) + .width(Length::Fill) + .height(Length::Fill) + .on_press(Message::RemoveNotify(id, self.unit.id, counter, hidden)) + .into(), + Some(ImageInfo::Data { + width, + height, + pixels, + }) => button(row![ + image(image::Handle::from_pixels( + width as u32, + height as u32, + pixels + )), + Space::with_width(4.), + column![ + text(notify.summery.clone()) + .shaping(text::Shaping::Advanced) + .size(20) + .font(Font { + weight: iced::font::Weight::Bold, + ..Default::default() + }), + text(notify.body.clone()).shaping(text::Shaping::Advanced) + ] + ]) + .width(Length::Fill) + .height(Length::Fill) + .style(iced::theme::Button::Secondary) + .on_press(Message::RemoveNotify(id, self.unit.id, counter, hidden)) + .into(), + Some(ImageInfo::Png(path)) | Some(ImageInfo::Jpg(path)) => button(row![ + image(image::Handle::from_path(path)).height(Length::Fill), + Space::with_width(4.), + column![ + text(notify.summery.clone()) + .shaping(text::Shaping::Advanced) + .size(20) + .font(Font { + weight: iced::font::Weight::Bold, + ..Default::default() + }), + text(notify.body.clone()).shaping(text::Shaping::Advanced) + ] + ]) + .width(Length::Fill) + .height(Length::Fill) + .style(iced::theme::Button::Secondary) + .on_press(Message::RemoveNotify(id, self.unit.id, counter, hidden)) + .into(), + _ => button(column![ + text(notify.summery.clone()).shaping(text::Shaping::Advanced), + text(notify.body.clone()).shaping(text::Shaping::Advanced) + ]) + .width(Length::Fill) + .height(Length::Fill) + .style(iced::theme::Button::Secondary) + .on_press(Message::RemoveNotify(id, self.unit.id, counter, hidden)) + .into(), + } + } +} + +#[allow(unused)] +#[derive(Debug)] +enum NotifyCommand { + ActionInvoked { id: u32, action_key: String }, + InlineReply { id: u32, text: String }, + NotificationClosed { id: u32, reason: u32 }, +} + +struct LalaMusicBar { + service_data: Option, + left: i64, + right: i64, + bar_index: SliderIndex, + launcher: Option, + launcherid: Option, + hidenid: Option, + right_panel: Option, + notifications: HashMap, + hidden_notifications: Vec, + sender: Sender, + receiver: Arc>>, +} + +#[derive(Copy, Clone, Default)] +enum SliderIndex { + #[default] + Balance, + Left, + Right, +} + +impl SliderIndex { + fn next(&self) -> Self { + match self { + SliderIndex::Balance => SliderIndex::Left, + SliderIndex::Left => SliderIndex::Right, + SliderIndex::Right => SliderIndex::Balance, + } + } + fn pre(&self) -> Self { + match self { + SliderIndex::Balance => SliderIndex::Right, + SliderIndex::Left => SliderIndex::Balance, + SliderIndex::Right => SliderIndex::Left, + } + } +} + +impl LalaMusicBar { + fn balance_percent(&self) -> u8 { + if self.left == 0 && self.right == 0 { + return 0; + } + (self.right * 100 / (self.left + self.right)) + .try_into() + .unwrap() + } + + fn update_balance(&mut self) { + self.left = aximer::get_left().unwrap_or(0); + self.right = aximer::get_right().unwrap_or(0); + } + + fn set_balance(&mut self, balance: u8) { + self.update_balance(); + let total = self.left + self.right; + self.right = total * balance as i64 / 100; + self.left = total - self.right; + aximer::set_left(self.left); + aximer::set_right(self.right); + } +} + +impl LalaMusicBar { + fn balance_bar(&self) -> Element { + let show_text = format!("balance {}%", self.balance_percent()); + row![ + button("<").on_press(Message::SliderIndexPre), + Space::with_width(Length::Fixed(1.)), + text(&show_text), + Space::with_width(Length::Fixed(10.)), + slider(0..=100, self.balance_percent(), Message::BalanceChanged), + Space::with_width(Length::Fixed(10.)), + button( + svg(svg::Handle::from_memory(RESET_SVG)) + .height(25.) + .width(25.) + ) + .height(31.) + .width(31.) + .on_press(Message::BalanceChanged(50)), + Space::with_width(Length::Fixed(1.)), + button(">").on_press(Message::SliderIndexNext) + ] + .into() + } + fn left_bar(&self) -> Element { + let show_text = format!("left {}%", self.left); + row![ + button("<").on_press(Message::SliderIndexPre), + Space::with_width(Length::Fixed(1.)), + text(&show_text), + Space::with_width(Length::Fixed(10.)), + slider(0..=100, self.left as u8, Message::UpdateLeft), + Space::with_width(Length::Fixed(10.)), + button(">").on_press(Message::SliderIndexNext) + ] + .into() + } + fn right_bar(&self) -> Element { + let show_text = format!("right {}%", self.right); + row![ + button("<").on_press(Message::SliderIndexPre), + Space::with_width(Length::Fixed(1.)), + text(&show_text), + Space::with_width(Length::Fixed(10.)), + slider(0..=100, self.right as u8, Message::UpdateRight), + Space::with_width(Length::Fixed(10.)), + button(">").on_press(Message::SliderIndexNext) + ] + .into() + } + + fn sound_slider(&self) -> Element { + match self.bar_index { + SliderIndex::Left => self.left_bar(), + SliderIndex::Right => self.right_bar(), + SliderIndex::Balance => self.balance_bar(), + } + } + + fn right_panel_view(&self) -> Element { + let btns: Vec> = self + .hidden_notifications + .iter() + .map(|wdgetinfo| { + container(wdgetinfo.button(None, true)) + .height(Length::Fixed(100.)) + .into() + }) + .collect(); + let mut view_elements: Vec> = vec![]; + + if let Some(data) = &self.service_data { + if let Some(art_url) = url::Url::parse(&data.metadata.mpris_arturl) + .ok() + .and_then(|url| url.to_file_path().ok()) + { + view_elements.push( + container(image(image::Handle::from_path(art_url)).width(Length::Fill)) + .padding(10) + .width(Length::Fill) + .into(), + ); + view_elements.push(Space::with_height(10.).into()); + view_elements.push( + container( + text(&data.metadata.xesam_title) + .size(20) + .font(Font { + weight: iced::font::Weight::Bold, + ..Default::default() + }) + .shaping(text::Shaping::Advanced) + .style(iced::theme::Text::Color(iced::Color::WHITE)), + ) + .into(), + ); + view_elements.push(Space::with_height(10.).into()); + } + } + view_elements.append(&mut vec![ + scrollable(column(btns).spacing(10.)) + .height(Length::Fill) + .into(), + container(button(text("clear all")).on_press(Message::ClearAllNotifications)) + .width(Length::Fill) + .center_x() + .into(), + Space::with_height(10.).into(), + ]); + column(view_elements).into() + } +} + +#[derive(Debug, Clone)] +enum Message { + RequestPre, + RequestNext, + RequestPause, + RequestPlay, + RequestDBusInfoUpdate, + UpdateBalance, + DBusInfoUpdate(Option), + BalanceChanged(u8), + UpdateLeft(u8), + UpdateRight(u8), + SliderIndexNext, + SliderIndexPre, + ToggleLauncher, + ToggleRightPanel, + LauncherInfo(LaunchMessage), + Notify(NotifyMessage), + RemoveNotify(Option, u32, usize, bool), + InlineReply((iced::window::Id, u32, String)), + InlineReplyMsgUpdate((iced::window::Id, String)), + CheckOutput, + ClearAllNotifications, +} + +impl From for Message { + fn from(value: NotifyMessage) -> Self { + Self::Notify(value) + } +} + +async fn get_metadata_initial() -> Option { + zbus_mpirs::init_mpirs().await.ok(); + let infos = zbus_mpirs::MPIRS_CONNECTIONS.lock().await; + infos.first().cloned() +} + +async fn get_metadata() -> Option { + let infos = zbus_mpirs::MPIRS_CONNECTIONS.lock().await; + infos.first().cloned() +} + +impl LalaMusicBar { + fn main_view(&self) -> Element { + let title = self + .service_data + .as_ref() + .map(|data| data.metadata.xesam_title.as_str()) + .unwrap_or("No Video here"); + let art_url = self + .service_data + .as_ref() + .and_then(|data| url::Url::parse(&data.metadata.mpris_arturl).ok()) + .and_then(|url| url.to_file_path().ok()); + let title = container( + text(title) + .size(20) + .font(Font { + weight: iced::font::Weight::Bold, + ..Default::default() + }) + .shaping(text::Shaping::Advanced) + .style(iced::theme::Text::Color(iced::Color::WHITE)), + ) + .width(Length::Fill) + .center_x(); + let can_play = self.service_data.as_ref().is_some_and(|data| data.can_play); + let can_pause = self + .service_data + .as_ref() + .is_some_and(|data| data.can_pause); + let can_go_next = self + .service_data + .as_ref() + .is_some_and(|data| data.can_go_next); + let can_go_pre = self + .service_data + .as_ref() + .is_some_and(|data| data.can_go_previous); + let mut button_pre = button("<|"); + if can_go_pre { + button_pre = button_pre.on_press(Message::RequestPre); + } + let mut button_next = button("|>"); + if can_go_next { + button_next = button_next.on_press(Message::RequestNext); + } + let button_play = { + match self.service_data { + Some(ref data) => { + if data.playback_status == "Playing" { + let mut btn = button(text("Pause")); + if can_pause { + btn = btn.on_press(Message::RequestPause); + } + btn + } else { + let mut btn = button(text("Play")); + if can_play { + btn = btn.on_press(Message::RequestPlay); + } + btn + } + } + None => button(text("Nothing todo")), + } + }; + let buttons = container(row![button_pre, button_play, button_next].spacing(5)) + .width(Length::Fill) + .center_x(); + + let sound_slider = self.sound_slider(); + let panel_text = if self.right_panel.is_some() { ">" } else { "<" }; + let col = if let Some(art_url) = art_url { + row![ + button( + svg(svg::Handle::from_memory(LAUNCHER_SVG)) + .width(25.) + .height(25.) + ) + .on_press(Message::ToggleLauncher), + Space::with_width(Length::Fixed(5.)), + image(image::Handle::from_path(art_url)), + title, + Space::with_width(Length::Fill), + buttons, + sound_slider, + Space::with_width(Length::Fixed(10.)), + button(text(panel_text)).on_press(Message::ToggleRightPanel) + ] + .spacing(10) + } else { + row![ + button( + svg(svg::Handle::from_memory(LAUNCHER_SVG)) + .width(25.) + .height(25.) + ) + .on_press(Message::ToggleLauncher), + title, + Space::with_width(Length::Fill), + buttons, + sound_slider, + Space::with_width(Length::Fixed(10.)), + button(text(panel_text)).on_press(Message::ToggleRightPanel) + ] + .spacing(10) + }; + + container(col) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +impl MultiApplication for LalaMusicBar { + type Message = Message; + type Flags = (); + type Executor = executor::Default; + type Theme = Theme; + type WindowInfo = LaLaInfo; + + fn new(_flags: Self::Flags) -> (Self, Command) { + let (sender, receiver) = channel::(100); + ( + Self { + service_data: None, + left: aximer::get_left().unwrap_or(0), + right: aximer::get_right().unwrap_or(0), + bar_index: SliderIndex::Balance, + launcher: None, + launcherid: None, + right_panel: None, + hidenid: None, + notifications: HashMap::new(), + hidden_notifications: Vec::new(), + sender, + receiver: Arc::new(Mutex::new(receiver)), + }, + Command::perform(get_metadata_initial(), Message::DBusInfoUpdate), + ) + } + + fn namespace(&self) -> String { + String::from("Mpirs_panel") + } + + fn id_info(&self, id: iced::window::Id) -> Option { + if self.launcherid.is_some_and(|tid| tid == id) { + Some(LaLaInfo::Launcher) + } else if self.hidenid.is_some_and(|tid| tid == id) { + Some(LaLaInfo::HiddenInfo) + } else if self.right_panel.is_some_and(|tid| tid == id) { + Some(LaLaInfo::RightPanel) + } else { + self.notifications + .get(&id) + .cloned() + .map(|notifyw| LaLaInfo::Notify(Box::new(notifyw))) + } + } + + fn set_id_info(&mut self, id: iced::window::Id, info: Self::WindowInfo) { + match info { + LaLaInfo::Launcher => { + self.launcherid = Some(id); + } + LaLaInfo::Notify(notify) => { + self.notifications.entry(id).or_insert(*notify); + } + LaLaInfo::HiddenInfo => { + self.hidenid = Some(id); + } + LaLaInfo::RightPanel => self.right_panel = Some(id), + } + } + + fn remove_id(&mut self, id: iced::window::Id) { + if self.launcherid.is_some_and(|lid| lid == id) { + self.launcherid.take(); + } + if self.right_panel.is_some_and(|lid| lid == id) { + self.right_panel.take(); + } + if self.hidenid.is_some_and(|lid| lid == id) { + self.hidenid.take(); + } + self.notifications.remove(&id); + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::DBusInfoUpdate(data) => self.service_data = data, + Message::RequestDBusInfoUpdate => { + return Command::perform(get_metadata(), Message::DBusInfoUpdate) + } + Message::RequestPlay => { + if let Some(ref data) = self.service_data { + if !data.can_play { + return Command::none(); + } + let data = data.clone(); + return Command::perform( + async move { + data.play().await.ok(); + get_metadata().await + }, + Message::DBusInfoUpdate, + ); + } + } + Message::RequestPause => { + if let Some(ref data) = self.service_data { + if !data.can_pause { + return Command::none(); + } + let data = data.clone(); + return Command::perform( + async move { + data.pause().await.ok(); + get_metadata().await + }, + Message::DBusInfoUpdate, + ); + } + } + Message::RequestPre => { + if let Some(ref data) = self.service_data { + if !data.can_go_previous { + return Command::none(); + } + let data = data.clone(); + return Command::perform( + async move { + data.go_previous().await.ok(); + get_metadata().await + }, + Message::DBusInfoUpdate, + ); + } + } + Message::RequestNext => { + if let Some(ref data) = self.service_data { + if !data.can_go_next { + return Command::none(); + } + let data = data.clone(); + return Command::perform( + async move { + data.go_next().await.ok(); + get_metadata().await + }, + Message::DBusInfoUpdate, + ); + } + } + Message::BalanceChanged(balance) => { + let current_balance = self.balance_percent(); + if current_balance == 0 { + return Command::none(); + } + self.set_balance(balance) + } + Message::UpdateBalance => { + self.update_balance(); + } + Message::UpdateLeft(percent) => { + aximer::set_left(percent as i64); + self.update_balance(); + } + Message::UpdateRight(percent) => { + aximer::set_right(percent as i64); + self.update_balance(); + } + Message::SliderIndexNext => self.bar_index = self.bar_index.next(), + Message::SliderIndexPre => self.bar_index = self.bar_index.pre(), + Message::ToggleLauncher => { + if self.launcher.is_some() { + if let Some(id) = self.launcherid { + self.launcher.take(); + return Command::single(Action::Window(WindowAction::Close(id))); + } + return Command::none(); + } + self.launcher = Some(Launcher::new()); + return Command::batch(vec![ + Command::single( + LaLaShellIdAction::new( + iced::window::Id::MAIN, + LalaShellAction::NewLayerShell(( + NewLayerShellSettings { + size: Some((500, 700)), + exclusive_zone: None, + anchor: Anchor::Left | Anchor::Bottom, + layer: Layer::Top, + margin: None, + keyboard_interactivity: KeyboardInteractivity::Exclusive, + use_last_output: false, + }, + LaLaInfo::Launcher, + )), + ) + .into(), + ), + self.launcher.as_ref().unwrap().focus_input(), + ]); + } + Message::ToggleRightPanel => { + if self.right_panel.is_some() { + if let Some(id) = self.right_panel { + self.right_panel.take(); + return Command::single(Action::Window(WindowAction::Close(id))); + } + return Command::none(); + } + return Command::single( + LaLaShellIdAction::new( + iced::window::Id::MAIN, + LalaShellAction::NewLayerShell(( + NewLayerShellSettings { + size: Some((300, 0)), + exclusive_zone: Some(300), + anchor: Anchor::Right | Anchor::Bottom | Anchor::Top, + layer: Layer::Top, + margin: None, + keyboard_interactivity: KeyboardInteractivity::None, + use_last_output: false, + }, + LaLaInfo::RightPanel, + )), + ) + .into(), + ); + } + Message::Notify(NotifyMessage::UnitAdd(notify)) => { + let mut commands = vec![]; + for unit in self.hidden_notifications.iter_mut() { + unit.upper += 135; + unit.counter += 1; + } + + for (id, unit) in self.notifications.iter_mut() { + unit.upper += 135; + unit.counter += 1; + if unit.counter > 3 { + self.hidden_notifications.push(unit.clone()); + commands.push(Command::single(Action::Window(WindowAction::Close(*id)))); + } else { + commands.push(Command::single( + LaLaShellIdAction::new( + *id, + LalaShellAction::MarginChange((unit.upper, 10, 10, 10)), + ) + .into(), + )); + } + } + + commands.push(Command::single( + LaLaShellIdAction::new( + iced::window::Id::MAIN, + LalaShellAction::NewLayerShell(( + NewLayerShellSettings { + size: Some((300, 130)), + exclusive_zone: None, + anchor: Anchor::Right | Anchor::Top, + layer: Layer::Top, + margin: Some((10, 10, 10, 10)), + keyboard_interactivity: KeyboardInteractivity::OnDemand, + use_last_output: true, + }, + LaLaInfo::Notify(Box::new(NotifyUnitWidgetInfo { + counter: 0, + upper: 10, + inline_reply: String::new(), + unit: notify, + })), + )), + ) + .into(), + )); + + if !self.hidden_notifications.is_empty() && self.hidenid.is_none() { + commands.push(Command::single( + LaLaShellIdAction::new( + iced::window::Id::MAIN, + LalaShellAction::NewLayerShell(( + NewLayerShellSettings { + size: Some((300, 25)), + exclusive_zone: None, + anchor: Anchor::Right | Anchor::Top, + layer: Layer::Top, + margin: Some((EXTRAINF_MARGIN, 10, 10, 10)), + keyboard_interactivity: KeyboardInteractivity::OnDemand, + use_last_output: true, + }, + LaLaInfo::HiddenInfo, + )), + ) + .into(), + )); + } + return Command::batch(commands); + } + Message::Notify(NotifyMessage::UnitRemove(removed_id)) => { + let removed_ids: Vec = self + .notifications + .iter() + .filter(|(_, info)| { + let NotifyUnit { id, .. } = info.unit; + removed_id == id + }) + .map(|(id, _)| *id) + .collect(); + let mut commands: Vec<_> = self + .notifications + .iter() + .filter(|(_, info)| { + let NotifyUnit { id, .. } = info.unit; + removed_id == id + }) + .map(|(id, _)| Command::single(Action::Window(WindowAction::Close(*id)))) + .collect(); + + let mut removed_counters = vec![]; + for id in removed_ids.iter() { + if let Some(NotifyUnitWidgetInfo { counter, .. }) = + self.notifications.remove(id) + { + removed_counters.push(counter); + } + } + removed_counters.sort(); + removed_counters.reverse(); + for counter in removed_counters { + for (_, unit) in self.notifications.iter_mut() { + if unit.counter > counter { + unit.counter -= 1; + unit.upper -= 135; + } + } + } + + for (id, unit) in self.notifications.iter() { + commands.push(Command::single( + LaLaShellIdAction::new( + *id, + LalaShellAction::MarginChange((unit.upper, 10, 10, 10)), + ) + .into(), + )); + } + + let mut remove_hided_notifications_count: Vec = self + .hidden_notifications + .iter() + .filter( + |NotifyUnitWidgetInfo { + unit: NotifyUnit { id, .. }, + .. + }| *id == removed_id, + ) + .map(|NotifyUnitWidgetInfo { counter, .. }| *counter) + .collect(); + + if self.notifications.len() < 3 { + for index in 0..self.notifications.len() { + remove_hided_notifications_count.push(index); + } + } + remove_hided_notifications_count.sort(); + remove_hided_notifications_count.reverse(); + + self.hidden_notifications.retain( + |NotifyUnitWidgetInfo { + unit: NotifyUnit { id, .. }, + .. + }| *id != removed_id, + ); + + for count in remove_hided_notifications_count { + for unit in self.hidden_notifications.iter_mut() { + if unit.counter > count { + unit.counter -= 1; + unit.upper -= 135; + } + } + } + for notify in self.hidden_notifications.iter() { + if notify.counter <= 4 { + commands.push(Command::single( + LaLaShellIdAction::new( + iced::window::Id::MAIN, + LalaShellAction::NewLayerShell(( + NewLayerShellSettings { + size: Some((300, 130)), + exclusive_zone: None, + anchor: Anchor::Right | Anchor::Top, + layer: Layer::Top, + margin: Some((notify.upper, 10, 10, 10)), + keyboard_interactivity: KeyboardInteractivity::OnDemand, + use_last_output: true, + }, + LaLaInfo::Notify(Box::new(notify.clone())), + )), + ) + .into(), + )); + } + } + + self.hidden_notifications + .retain(|NotifyUnitWidgetInfo { counter, .. }| *counter > 4); + + if self.hidden_notifications.is_empty() && self.hidenid.is_some() { + let hidenid = self.hidenid.unwrap(); + + commands.push(Command::single(Action::Window(WindowAction::Close( + hidenid, + )))); + } + + commands.push(Command::perform(async {}, |_| Message::CheckOutput)); + + return Command::batch(commands); + } + Message::RemoveNotify(id, notify_id, counter, is_hidden) => { + self.sender + .try_send(NotifyCommand::ActionInvoked { + id: notify_id, + action_key: DEFAULT_ACTION.to_string(), + }) + .ok(); + + let mut commands = vec![]; + + if !is_hidden { + let removed_pos = self + .notifications + .iter() + .find(|(oid, _)| id.is_some_and(|id| id == **oid)) + .map(|(_, info)| info.upper) + .unwrap_or(0); + for (id, unit) in self.notifications.iter_mut() { + if unit.upper > removed_pos { + unit.upper -= 135; + } + if unit.counter > counter { + unit.counter -= 1; + } + commands.push(Command::single( + LaLaShellIdAction::new( + *id, + LalaShellAction::MarginChange((unit.upper, 10, 10, 10)), + ) + .into(), + )); + } + } + let mut to_show_id = None; + + let mut to_removed_index = None; + for (index, notify) in self.hidden_notifications.iter_mut().enumerate() { + if counter > notify.counter { + continue; + } + if counter == notify.counter { + to_removed_index = Some(index); + } + notify.counter -= 1; + notify.upper -= 135; + if notify.counter == 3 && !is_hidden { + to_show_id = Some(index); + commands.push(Command::single( + LaLaShellIdAction::new( + iced::window::Id::MAIN, + LalaShellAction::NewLayerShell(( + NewLayerShellSettings { + size: Some((300, 130)), + exclusive_zone: None, + anchor: Anchor::Right | Anchor::Top, + layer: Layer::Top, + margin: Some((notify.upper, 10, 10, 10)), + keyboard_interactivity: KeyboardInteractivity::OnDemand, + use_last_output: true, + }, + LaLaInfo::Notify(Box::new(notify.clone())), + )), + ) + .into(), + )); + } + } + + if is_hidden { + if let Some(index) = to_removed_index { + self.hidden_notifications.remove(index); + } + } + + if let Some(index) = to_show_id { + self.hidden_notifications.remove(index); + } + + if self.hidden_notifications.is_empty() && self.hidenid.is_some() { + let hidenid = self.hidenid.unwrap(); + + commands.push(Command::single(Action::Window(WindowAction::Close( + hidenid, + )))); + } + + if let Some(id) = id { + commands.push(Command::single(Action::Window(WindowAction::Close(id)))); + } + commands.push(Command::perform(async {}, |_| Message::CheckOutput)); + + return Command::batch(commands); + } + Message::CheckOutput => { + if self.notifications.is_empty() { + return Command::single( + LaLaShellIdAction::new( + iced::window::Id::MAIN, + LalaShellAction::ForgetLastOutput, + ) + .into(), + ); + } + } + Message::LauncherInfo(message) => { + if let Some(launcher) = self.launcher.as_mut() { + if let Some(id) = self.launcherid { + let cmd = launcher.update(message, id); + if launcher.should_delete { + self.launcher.take(); + } + return cmd; + } + } + } + Message::InlineReply((id, notify_id, text)) => { + self.sender + .try_send(NotifyCommand::InlineReply { + id: notify_id, + text, + }) + .ok(); + let removed_pos = self + .notifications + .iter() + .find(|(oid, _)| **oid == id) + .map(|(_, info)| info.upper) + .unwrap_or(0); + + let mut commands = vec![]; + for (id, unit) in self.notifications.iter_mut() { + if unit.upper > removed_pos { + unit.upper -= 135; + unit.counter -= 1; + } + commands.push(Command::single( + LaLaShellIdAction::new( + *id, + LalaShellAction::MarginChange((unit.upper, 10, 10, 10)), + ) + .into(), + )); + } + commands.append(&mut vec![ + Command::single(Action::Window(WindowAction::Close(id))), + Command::perform(async {}, |_| Message::CheckOutput), + ]); + + return Command::batch(commands); + } + Message::InlineReplyMsgUpdate((id, msg)) => { + let notify = self.notifications.get_mut(&id).unwrap(); + notify.inline_reply = msg; + } + Message::ClearAllNotifications => { + self.hidden_notifications.clear(); + let mut commands = self + .notifications + .keys() + .map(|id| Command::single(Action::Window(WindowAction::Close(*id)))) + .collect::>(); + + if let Some(id) = self.hidenid { + commands.push(Command::single(Action::Window(WindowAction::Close(id)))); + } + return Command::batch(commands); + } + } + Command::none() + } + + fn view(&self, id: iced::window::Id) -> Element { + if let Some(info) = self.id_info(id) { + match info { + LaLaInfo::Launcher => { + if let Some(launcher) = &self.launcher { + return launcher.view(); + } + } + LaLaInfo::Notify(unitwidgetinfo) => { + let btnwidgets: Element = unitwidgetinfo.button(Some(id), false); + + let notify = &unitwidgetinfo.unit; + let notifywidget = self.notifications.get(&id).unwrap(); + if notify.inline_reply_support() { + return column![ + btnwidgets, + Space::with_height(5.), + row![ + text_input("reply something", ¬ifywidget.inline_reply) + .on_input(move |msg| Message::InlineReplyMsgUpdate((id, msg))) + .on_submit(Message::InlineReply(( + id, + notify.id, + notifywidget.inline_reply.clone() + ))), + button("send").on_press(Message::InlineReply(( + id, + notify.id, + notifywidget.inline_reply.clone() + ))), + ] + ] + .into(); + } + return btnwidgets; + } + LaLaInfo::HiddenInfo => { + return text(format!( + "hidden notifications {}", + self.hidden_notifications.len() + )) + .into(); + } + LaLaInfo::RightPanel => { + return self.right_panel_view(); + } + } + } + self.main_view() + } + + fn subscription(&self) -> iced::Subscription { + let rv = self.receiver.clone(); + iced::subscription::Subscription::batch([ + iced::time::every(std::time::Duration::from_secs(1)) + .map(|_| Message::RequestDBusInfoUpdate), + iced::time::every(std::time::Duration::from_secs(5)).map(|_| Message::UpdateBalance), + iced::event::listen() + .map(|event| Message::LauncherInfo(LaunchMessage::IcedEvent(event))), + iced::subscription::channel(std::any::TypeId::of::<()>(), 100, |sender| async move { + let mut receiver = rv.lock().await; + let Ok(connection) = start_connection( + sender, + vec![ + "body".to_owned(), + "body-markup".to_owned(), + "actions".to_owned(), + "icon-static".to_owned(), + "x-canonical-private-synchronous".to_owned(), + "x-dunst-stack-tag".to_owned(), + "inline-reply".to_owned(), + ], + VersionInfo { + name: "LaLaMako".to_owned(), + vendor: "waycrate".to_owned(), + version: env!("CARGO_PKG_VERSION").to_owned(), + spec_version: env!("CARGO_PKG_VERSION_PATCH").to_owned(), + }, + ) + .await + else { + pending::<()>().await; + unreachable!() + }; + type LaLaMakoMusic = LaLaMako; + let Ok(lalaref) = connection + .object_server() + .interface::<_, LaLaMakoMusic>(NOTIFICATION_SERVICE_PATH) + .await + else { + pending::<()>().await; + unreachable!() + }; + + while let Some(cmd) = receiver.next().await { + match cmd { + NotifyCommand::ActionInvoked { id, action_key } => { + LaLaMakoMusic::action_invoked( + lalaref.signal_context(), + id, + &action_key, + ) + .await + .ok(); + } + NotifyCommand::InlineReply { id, text } => { + LaLaMakoMusic::notification_replied( + lalaref.signal_context(), + id, + &text, + ) + .await + .ok(); + } + NotifyCommand::NotificationClosed { id, reason } => { + LaLaMakoMusic::notification_closed( + lalaref.signal_context(), + id, + reason, + ) + .await + .ok(); + } + } + } + pending::<()>().await; + unreachable!() + }), + ]) + } + + fn theme(&self) -> Self::Theme { + Theme::TokyoNight + } +} diff --git a/src/zbus_mpirs.rs b/lala_bar/src/zbus_mpirs.rs similarity index 98% rename from src/zbus_mpirs.rs rename to lala_bar/src/zbus_mpirs.rs index 2cbb1bd..4b6c01e 100644 --- a/src/zbus_mpirs.rs +++ b/lala_bar/src/zbus_mpirs.rs @@ -4,7 +4,7 @@ use std::{ }; use futures_util::StreamExt; -use once_cell::sync::Lazy; +use std::sync::LazyLock; use tokio::sync::Mutex; @@ -144,8 +144,8 @@ async fn get_connection() -> zbus::Result { } } -pub static MPIRS_CONNECTIONS: Lazy>>> = - Lazy::new(|| Arc::new(Mutex::new(Vec::new()))); +pub static MPIRS_CONNECTIONS: LazyLock>>> = + LazyLock::new(|| Arc::new(Mutex::new(Vec::new()))); async fn mpirs_is_ready_in(path: T) -> bool { let conns = MPIRS_CONNECTIONS.lock().await; @@ -376,7 +376,6 @@ pub async fn init_mpirs() -> Result<()> { .await .ok(); } - //println!("name: {:?}", get_mpirs_connections().await); } Ok::<(), anyhow::Error>(()) }); diff --git a/notification_iced/Cargo.toml b/notification_iced/Cargo.toml new file mode 100644 index 0000000..4f094fb --- /dev/null +++ b/notification_iced/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "notification_iced" +version = "0.1.0" +edition = "2021" + +[dependencies] +futures.workspace = true +serde.workspace = true +zbus.workspace = true +futures-util.workspace = true +url.workspace = true +glob = "0.3.1" diff --git a/notification_iced/src/lib.rs b/notification_iced/src/lib.rs new file mode 100644 index 0000000..164ae49 --- /dev/null +++ b/notification_iced/src/lib.rs @@ -0,0 +1,318 @@ +//! # D-Bus interface proxy for: `org.freedesktop.Notifications` +//! +//! This code was generated by `zbus-xmlgen` `4.1.0` from D-Bus introspection data. +//! Source: `Interface '/org/freedesktop/Notifications' from service 'org.freedesktop.Notifications' on system bus`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the zbus +//! documentation. +//! +//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the +//! following zbus API can be used: +//! +//! * [`zbus::fdo::PeerProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! * [`zbus::fdo::PropertiesProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use std::collections::HashMap; +use std::path::{Path, PathBuf}; + +use glob::glob; +use zbus::{interface, object_server::SignalContext, zvariant::OwnedValue}; + +use std::sync::{Arc, LazyLock, RwLock}; + +use futures::channel::mpsc::Sender; +use zbus::ConnectionBuilder; + +use zbus::zvariant::{SerializeDict, Type}; + +pub const NOTIFICATION_DELETED_BY_EXPIRED: u32 = 1; +pub const NOTIFICATION_DELETED_BY_USER: u32 = 2; + +pub const NOTIFICATION_CLOSED_BY_DBUS: u32 = 3; +pub const NOTIFICATION_CLOSED_BY_UNKNOWN_REASON: u32 = 4; + +static ICON_CACHE: LazyLock>>> = + LazyLock::new(|| Arc::new(RwLock::new(HashMap::new()))); + +#[derive(Type, Debug, SerializeDict, OwnedValue, Clone)] +pub struct ImageData { + width: i32, + height: i32, + rowstride: i32, + has_alpha: bool, + bits_per_sample: i32, + channels: i32, + data: Vec, +} + +#[derive(Debug, Clone)] +pub enum NotifyMessage { + UnitAdd(NotifyUnit), + UnitRemove(u32), +} + +fn lazy_get_icon(icon: &str) -> Option { + let icon_cache = ICON_CACHE.read().unwrap(); + if icon.contains(icon) { + return icon_cache.get(icon).cloned(); + } + drop(icon_cache); + let mut icon_cache = ICON_CACHE.write().unwrap(); + if let Some(path) = get_svg_icon("hicolor", icon) { + icon_cache.insert(icon.to_string(), ImageInfo::Svg(path.clone())); + return Some(ImageInfo::Svg(path)); + } + if let Some(path) = get_png_icon("hicolor", icon) { + icon_cache.insert(icon.to_string(), ImageInfo::Png(path.clone())); + return Some(ImageInfo::Png(path)); + } + if let Some(path) = get_jpeg_icon("hicolor", icon) { + icon_cache.insert(icon.to_string(), ImageInfo::Jpg(path.clone())); + return Some(ImageInfo::Jpg(path)); + } + None +} + +fn get_svg_icon(theme: &str, icon: &str) -> Option { + glob(&format!("/usr/share/icons/{theme}/**/**/{icon}.svg")) + .ok()? + .next()? + .ok() +} + +fn get_png_icon(theme: &str, icon: &str) -> Option { + glob(&format!("/usr/share/icons/{theme}/**/**/{icon}.png")) + .ok()? + .next()? + .ok() +} + +fn get_jpeg_icon(theme: &str, icon: &str) -> Option { + glob(&format!("/usr/share/icons/{theme}/**/**/{icon}.jpg")) + .ok()? + .next()? + .ok() +} + +#[derive(Debug, Clone)] +pub struct NotifyHint { + image_data: Option, + desktop_entry: Option, +} + +#[derive(Debug, Clone)] +pub enum ImageInfo { + Data { + width: i32, + height: i32, + pixels: Vec, + }, + Svg(PathBuf), + Png(PathBuf), + Jpg(PathBuf), +} + +impl NotifyHint { + fn desktop_image(&self) -> Option { + self.desktop_entry + .as_ref() + .and_then(|icon| lazy_get_icon(icon)) + } + fn hint_image(&self) -> Option { + self.image_data.as_ref().map(|data| ImageInfo::Data { + width: data.width, + height: data.height, + pixels: data.data.clone(), + }) + } +} + +#[derive(Debug, Clone)] +pub struct NotifyUnit { + pub app_name: String, + pub id: u32, + pub icon: String, + pub summery: String, + pub body: String, + pub actions: Vec, + pub timeout: i32, + pub hint: NotifyHint, +} + +impl NotifyUnit { + pub fn inline_reply_support(&self) -> bool { + self.actions.contains(&"inline-reply".to_owned()) + } + + pub fn image(&self) -> Option { + if let Some(hint_image) = self.hint.hint_image() { + return Some(hint_image); + } + if !self.icon.is_empty() { + let path = Path::new(&self.icon); + if path.exists() { + if self.icon.ends_with("svg") { + return Some(ImageInfo::Svg(path.into())); + } else if self.icon.ends_with("jpg") { + return Some(ImageInfo::Jpg(path.into())); + } else { + return Some(ImageInfo::Png(path.into())); + } + } + if let Some(info) = lazy_get_icon(&self.icon) { + return Some(info); + } + } + self.hint.desktop_image() + } +} + +#[derive(Debug, Clone)] +pub struct VersionInfo { + pub name: String, + pub vendor: String, + pub version: String, + pub spec_version: String, +} + +#[derive(Debug)] +pub struct LaLaMako + Send> { + capabilities: Vec, + sender: Sender, + version: VersionInfo, +} + +#[interface(name = "org.freedesktop.Notifications")] +impl + Send + 'static> LaLaMako { + // CloseNotification method + async fn close_notification( + &mut self, + #[zbus(signal_context)] ctx: SignalContext<'_>, + id: u32, + ) -> zbus::fdo::Result<()> { + Self::notification_closed(&ctx, id, NOTIFICATION_DELETED_BY_USER) + .await + .ok(); + self.sender + .try_send(NotifyMessage::UnitRemove(id).into()) + .ok(); + Ok(()) + } + + /// GetCapabilities method + fn get_capabilities(&self) -> Vec { + self.capabilities.clone() + } + + /// GetServerInformation method + fn get_server_information(&self) -> (String, String, String, String) { + let VersionInfo { + name, + vendor, + version, + spec_version, + } = &self.version; + ( + name.clone(), + vendor.clone(), + version.clone(), + spec_version.clone(), + ) + } + + // Notify method + #[allow(clippy::too_many_arguments)] + fn notify( + &mut self, + app_name: &str, + id: u32, + icon: &str, + summery: &str, + body: &str, + actions: Vec<&str>, + mut hints: std::collections::HashMap<&str, OwnedValue>, + timeout: i32, + ) -> zbus::fdo::Result { + let image_data: Option = + hints.remove("image-data").and_then(|v| v.try_into().ok()); + let desktop_entry: Option = hints + .remove("desktop-entry") + .and_then(|v| v.try_into().ok()); + self.sender + .try_send( + NotifyMessage::UnitAdd(NotifyUnit { + app_name: app_name.to_string(), + id, + icon: icon.to_string(), + summery: summery.to_string(), + body: body.to_string(), + actions: actions.iter().map(|a| a.to_string()).collect(), + timeout, + hint: NotifyHint { + image_data, + desktop_entry, + }, + }) + .into(), + ) + .ok(); + Ok(0) + } + + #[zbus(signal)] + pub async fn action_invoked( + ctx: &SignalContext<'_>, + id: u32, + action_key: &str, + ) -> zbus::Result<()>; + + #[zbus(signal)] + pub async fn notification_replied( + ctx: &SignalContext<'_>, + id: u32, + text: &str, + ) -> zbus::Result<()>; + + // NotificationClosed signal + #[zbus(signal)] + pub async fn notification_closed( + ctx: &SignalContext<'_>, + id: u32, + reason: u32, + ) -> zbus::Result<()>; +} + +pub const NOTIFICATION_SERVICE_PATH: &str = "/org/freedesktop/Notifications"; +pub const NOTIFICATION_SERVICE_NAME: &str = "/org/freedesktop/Notifications"; +pub const NOTIFICATION_SERVICE_INTERFACE: &str = "/org/freedesktop/Notifications"; + +pub const ACTION_INVOKED: &str = "action_invoked"; +pub const NOTIFICATION_CLOSED: &str = "notification_closed"; + +pub const DEFAULT_ACTION: &str = "default"; + +pub async fn start_connection + Send + 'static>( + sender: Sender, + capabilities: Vec, + version: VersionInfo, +) -> Result { + ConnectionBuilder::session()? + .name("org.freedesktop.Notifications")? + .serve_at( + "/org/freedesktop/Notifications", + LaLaMako { + sender, + capabilities, + version, + }, + )? + .build() + .await +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 3a32345..0000000 --- a/src/main.rs +++ /dev/null @@ -1,502 +0,0 @@ -use iced::widget::{button, container, image, row, slider, svg, text, Space}; -use iced::{executor, Event, Font}; -use iced::{Command, Element, Length, Theme}; -use iced_layershell::actions::{ - LayershellCustomActionsWithIdAndInfo, LayershellCustomActionsWithInfo, -}; -use launcher::Launcher; -use zbus_mpirs::ServiceInfo; - -use iced_layershell::reexport::{Anchor, KeyboardInteractivity, Layer, NewLayerShellSettings}; -use iced_layershell::settings::{LayerShellSettings, Settings}; -use iced_layershell::MultiApplication; -use iced_runtime::command::Action; -use iced_runtime::window::Action as WindowAction; - -mod aximer; -mod launcher; -mod zbus_mpirs; - -type LaLaShellIdAction = LayershellCustomActionsWithIdAndInfo; -type LalaShellAction = LayershellCustomActionsWithInfo; - -const LAUNCHER_SVG: &[u8] = include_bytes!("../misc/launcher.svg"); - -const RESET_SVG: &[u8] = include_bytes!("../misc/reset.svg"); - -pub fn main() -> Result<(), iced_layershell::Error> { - env_logger::builder().format_timestamp(None).init(); - - LalaMusicBar::run(Settings { - layer_settings: LayerShellSettings { - size: Some((0, 35)), - exclusize_zone: 35, - anchor: Anchor::Bottom | Anchor::Left | Anchor::Right, - layer: Layer::Top, - ..Default::default() - }, - ..Default::default() - }) -} - -#[derive(Debug, Clone, Copy)] -struct LauncherInfo; - -#[derive(Default)] -struct LalaMusicBar { - service_data: Option, - left: i64, - right: i64, - bar_index: SliderIndex, - launcher: Option, - launcherid: Option, -} - -#[derive(Copy, Clone, Default)] -enum SliderIndex { - #[default] - Balance, - Left, - Right, -} - -impl SliderIndex { - fn next(&self) -> Self { - match self { - SliderIndex::Balance => SliderIndex::Left, - SliderIndex::Left => SliderIndex::Right, - SliderIndex::Right => SliderIndex::Balance, - } - } - fn pre(&self) -> Self { - match self { - SliderIndex::Balance => SliderIndex::Right, - SliderIndex::Left => SliderIndex::Balance, - SliderIndex::Right => SliderIndex::Left, - } - } -} - -impl LalaMusicBar { - fn balance_percent(&self) -> u8 { - if self.left == 0 && self.right == 0 { - return 0; - } - (self.right * 100 / (self.left + self.right)) - .try_into() - .unwrap() - } - - fn update_balance(&mut self) { - self.left = aximer::get_left().unwrap_or(0); - self.right = aximer::get_right().unwrap_or(0); - } - - fn set_balance(&mut self, balance: u8) { - self.update_balance(); - let total = self.left + self.right; - self.right = total * balance as i64 / 100; - self.left = total - self.right; - aximer::set_left(self.left); - aximer::set_right(self.right); - } -} - -impl LalaMusicBar { - fn balance_bar(&self) -> Element { - let show_text = format!("balance {}%", self.balance_percent()); - row![ - button("<").on_press(Message::SliderIndexPre), - Space::with_width(Length::Fixed(1.)), - text(&show_text), - Space::with_width(Length::Fixed(10.)), - slider(0..=100, self.balance_percent(), Message::BalanceChanged), - Space::with_width(Length::Fixed(10.)), - button( - svg(svg::Handle::from_memory(RESET_SVG)) - .height(25.) - .width(25.) - ) - .height(31.) - .width(31.) - .on_press(Message::BalanceChanged(50)), - Space::with_width(Length::Fixed(1.)), - button(">").on_press(Message::SliderIndexNext) - ] - .into() - } - fn left_bar(&self) -> Element { - let show_text = format!("left {}%", self.left); - row![ - button("<").on_press(Message::SliderIndexPre), - Space::with_width(Length::Fixed(1.)), - text(&show_text), - Space::with_width(Length::Fixed(10.)), - slider(0..=100, self.left as u8, Message::UpdateLeft), - Space::with_width(Length::Fixed(10.)), - button(">").on_press(Message::SliderIndexNext) - ] - .into() - } - fn right_bar(&self) -> Element { - let show_text = format!("right {}%", self.right); - row![ - button("<").on_press(Message::SliderIndexPre), - Space::with_width(Length::Fixed(1.)), - text(&show_text), - Space::with_width(Length::Fixed(10.)), - slider(0..=100, self.right as u8, Message::UpdateRight), - Space::with_width(Length::Fixed(10.)), - button(">").on_press(Message::SliderIndexNext) - ] - .into() - } - - fn sound_slider(&self) -> Element { - match self.bar_index { - SliderIndex::Left => self.left_bar(), - SliderIndex::Right => self.right_bar(), - SliderIndex::Balance => self.balance_bar(), - } - } -} - -#[derive(Debug, Clone)] -enum Message { - RequestPre, - RequestNext, - RequestPause, - RequestPlay, - RequestDBusInfoUpdate, - UpdateBalance, - DBusInfoUpdate(Option), - BalanceChanged(u8), - UpdateLeft(u8), - UpdateRight(u8), - SliderIndexNext, - SliderIndexPre, - ToggleLauncher, - SearchEditChanged(String), - SearchSubmit, - Launch(usize), - IcedEvent(Event), -} - -async fn get_metadata_initial() -> Option { - zbus_mpirs::init_mpirs().await.ok(); - let infos = zbus_mpirs::MPIRS_CONNECTIONS.lock().await; - infos.first().cloned() -} - -async fn get_metadata() -> Option { - let infos = zbus_mpirs::MPIRS_CONNECTIONS.lock().await; - infos.first().cloned() -} - -impl LalaMusicBar { - fn main_view(&self) -> Element { - let title = self - .service_data - .as_ref() - .map(|data| data.metadata.xesam_title.as_str()) - .unwrap_or("No Video here"); - let art_url = self - .service_data - .as_ref() - .and_then(|data| url::Url::parse(&data.metadata.mpris_arturl).ok()) - .and_then(|url| url.to_file_path().ok()); - let title = container( - text(title) - .size(20) - .font(Font { - weight: iced::font::Weight::Bold, - ..Default::default() - }) - .shaping(text::Shaping::Advanced) - .style(iced::theme::Text::Color(iced::Color::WHITE)), - ) - .width(Length::Fill) - .center_x(); - let can_play = self.service_data.as_ref().is_some_and(|data| data.can_play); - let can_pause = self - .service_data - .as_ref() - .is_some_and(|data| data.can_pause); - let can_go_next = self - .service_data - .as_ref() - .is_some_and(|data| data.can_go_next); - let can_go_pre = self - .service_data - .as_ref() - .is_some_and(|data| data.can_go_previous); - let mut button_pre = button("<|"); - if can_go_pre { - button_pre = button_pre.on_press(Message::RequestPre); - } - let mut button_next = button("|>"); - if can_go_next { - button_next = button_next.on_press(Message::RequestNext); - } - let button_play = { - match self.service_data { - Some(ref data) => { - if data.playback_status == "Playing" { - let mut btn = button(text("Pause")); - if can_pause { - btn = btn.on_press(Message::RequestPause); - } - btn - } else { - let mut btn = button(text("Play")); - if can_play { - btn = btn.on_press(Message::RequestPlay); - } - btn - } - } - None => button(text("Nothing todo")), - } - }; - let buttons = container(row![button_pre, button_play, button_next].spacing(5)) - .width(Length::Fill) - .center_x(); - - let sound_slider = self.sound_slider(); - let col = if let Some(art_url) = art_url { - row![ - button( - svg(svg::Handle::from_memory(LAUNCHER_SVG)) - .width(25.) - .height(25.) - ) - .on_press(Message::ToggleLauncher), - Space::with_width(Length::Fixed(5.)), - image(image::Handle::from_path(art_url)), - title, - Space::with_width(Length::Fill), - buttons, - sound_slider, - Space::with_width(Length::Fixed(10.)), - ] - .spacing(10) - } else { - row![ - button( - svg(svg::Handle::from_memory(LAUNCHER_SVG)) - .width(25.) - .height(25.) - ) - .on_press(Message::ToggleLauncher), - title, - Space::with_width(Length::Fill), - buttons, - sound_slider, - Space::with_width(Length::Fixed(10.)), - ] - .spacing(10) - }; - - container(col) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into() - } -} - -impl MultiApplication for LalaMusicBar { - type Message = Message; - type Flags = (); - type Executor = executor::Default; - type Theme = Theme; - type WindowInfo = LauncherInfo; - - fn new(_flags: Self::Flags) -> (Self, Command) { - ( - Self { - service_data: None, - left: aximer::get_left().unwrap_or(0), - right: aximer::get_right().unwrap_or(0), - bar_index: SliderIndex::Balance, - launcher: None, - launcherid: None, - }, - Command::perform(get_metadata_initial(), Message::DBusInfoUpdate), - ) - } - - fn namespace(&self) -> String { - String::from("Mpirs_panel") - } - - fn id_info(&self, id: iced_futures::core::window::Id) -> Option<&Self::WindowInfo> { - if self.launcherid.is_some_and(|tid| tid == id) { - Some(&LauncherInfo) - } else { - None - } - } - - fn set_id_info(&mut self, id: iced_futures::core::window::Id, _info: Self::WindowInfo) { - self.launcherid = Some(id); - } - - fn remove_id(&mut self, _id: iced_futures::core::window::Id) { - self.launcherid.take(); - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::DBusInfoUpdate(data) => self.service_data = data, - Message::RequestDBusInfoUpdate => { - return Command::perform(get_metadata(), Message::DBusInfoUpdate) - } - Message::RequestPlay => { - if let Some(ref data) = self.service_data { - if !data.can_play { - return Command::none(); - } - let data = data.clone(); - return Command::perform( - async move { - data.play().await.ok(); - get_metadata().await - }, - Message::DBusInfoUpdate, - ); - } - } - Message::RequestPause => { - if let Some(ref data) = self.service_data { - if !data.can_pause { - return Command::none(); - } - let data = data.clone(); - return Command::perform( - async move { - data.pause().await.ok(); - get_metadata().await - }, - Message::DBusInfoUpdate, - ); - } - } - Message::RequestPre => { - if let Some(ref data) = self.service_data { - if !data.can_go_previous { - return Command::none(); - } - let data = data.clone(); - return Command::perform( - async move { - data.go_previous().await.ok(); - get_metadata().await - }, - Message::DBusInfoUpdate, - ); - } - } - Message::RequestNext => { - if let Some(ref data) = self.service_data { - if !data.can_go_next { - return Command::none(); - } - let data = data.clone(); - return Command::perform( - async move { - data.go_next().await.ok(); - get_metadata().await - }, - Message::DBusInfoUpdate, - ); - } - } - Message::BalanceChanged(balance) => { - let current_balance = self.balance_percent(); - if current_balance == 0 { - return Command::none(); - } - self.set_balance(balance) - } - Message::UpdateBalance => { - self.update_balance(); - } - Message::UpdateLeft(percent) => { - aximer::set_left(percent as i64); - self.update_balance(); - } - Message::UpdateRight(percent) => { - aximer::set_right(percent as i64); - self.update_balance(); - } - Message::SliderIndexNext => self.bar_index = self.bar_index.next(), - Message::SliderIndexPre => self.bar_index = self.bar_index.pre(), - Message::ToggleLauncher => { - if self.launcher.is_some() { - if let Some(id) = self.launcherid { - self.launcher.take(); - return Command::single(Action::Window(WindowAction::Close(id))); - } - return Command::none(); - } - self.launcher = Some(Launcher::new()); - return Command::batch(vec![ - Command::single( - LaLaShellIdAction::new( - iced::window::Id::MAIN, - LalaShellAction::NewLayerShell(( - NewLayerShellSettings { - size: Some((500, 700)), - exclusize_zone: None, - anchor: Anchor::Left | Anchor::Bottom, - layer: Layer::Top, - margins: None, - keyboard_interactivity: KeyboardInteractivity::Exclusive, - }, - LauncherInfo, - )), - ) - .into(), - ), - self.launcher.as_ref().unwrap().focus_input(), - ]); - } - _ => { - if let Some(launcher) = self.launcher.as_mut() { - if let Some(id) = self.launcherid { - let cmd = launcher.update(message, id); - if launcher.should_delete { - self.launcher.take(); - } - return cmd; - } - } - } - } - Command::none() - } - - fn view(&self, id: iced::window::Id) -> Element { - if let Some(LauncherInfo) = self.id_info(id) { - if let Some(launcher) = &self.launcher { - return launcher.view(); - } - } - self.main_view() - } - - fn subscription(&self) -> iced::Subscription { - iced::subscription::Subscription::batch([ - iced::time::every(std::time::Duration::from_secs(1)) - .map(|_| Message::RequestDBusInfoUpdate), - iced::time::every(std::time::Duration::from_secs(5)).map(|_| Message::UpdateBalance), - iced::event::listen().map(Message::IcedEvent), - ]) - } - - fn theme(&self) -> Self::Theme { - Theme::TokyoNight - } -}