diff --git a/Cargo.lock b/Cargo.lock index 5f4421ea..e52af870 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,11 +246,14 @@ dependencies = [ "async-io", "freedesktop-desktop-entry", "futures-util", + "gtk4", "libcosmic-widgets", "libpulse-binding", "mpris2-zbus", + "once_cell", "pulsectl-rs", - "relm4", + "relm4-macros 0.4.4", + "tokio", "tracker", "zbus", ] @@ -1071,7 +1074,7 @@ version = "0.1.0" source = "git+https://github.com/pop-os/libcosmic?branch=lucy/widgets#d004d686bd83e55f8f5058700941284ca84dc579" dependencies = [ "relm4", - "relm4-macros 0.4.1 (git+https://github.com/AaronErhardt/relm4?branch=new-approach)", + "relm4-macros 0.4.1", "tracker", ] @@ -1660,7 +1663,6 @@ dependencies = [ "gtk4", "log", "once_cell", - "relm4-macros 0.4.1 (git+https://github.com/AaronErhardt/relm4?rev=7404ad64ca8763f6629cadcd743947cd29e1538a)", "tokio", ] @@ -1674,16 +1676,6 @@ dependencies = [ "syn", ] -[[package]] -name = "relm4-macros" -version = "0.4.1" -source = "git+https://github.com/AaronErhardt/relm4?rev=7404ad64ca8763f6629cadcd743947cd29e1538a#7404ad64ca8763f6629cadcd743947cd29e1538a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "relm4-macros" version = "0.4.4" diff --git a/applets/cosmic-applet-audio/Cargo.toml b/applets/cosmic-applet-audio/Cargo.toml index 47546d38..6f88f5a1 100644 --- a/applets/cosmic-applet-audio/Cargo.toml +++ b/applets/cosmic-applet-audio/Cargo.toml @@ -5,17 +5,18 @@ edition = "2021" license = "LGPL-3.0-or-later" [dependencies] -async-io = "1.6.0" futures-util = "0.3.21" libcosmic-widgets = { git = "https://github.com/pop-os/libcosmic", branch = "lucy/widgets" } libpulse-binding = "2.26.0" pulsectl-rs = "0.3.2" -relm4 = { git = "https://github.com/AaronErhardt/relm4", rev = "7404ad64ca8763f6629cadcd743947cd29e1538a", features = [ - "macros", -] } tracker = "0.1.1" freedesktop-desktop-entry = "0.5.0" mpris2-zbus = { git = "https://github.com/pop-os/mpris2-zbus" } zbus = "2.1.1" +tokio = { version = "1.17.0", features = ["full"] } +relm4-macros = "0.4.4" +once_cell = "1.10.0" +gtk4 = { version = "0.4.7", features = ["v4_2"] } +async-io = "1.6.0" [features] diff --git a/applets/cosmic-applet-audio/src/main.rs b/applets/cosmic-applet-audio/src/main.rs index e8628a12..3e72b17f 100644 --- a/applets/cosmic-applet-audio/src/main.rs +++ b/applets/cosmic-applet-audio/src/main.rs @@ -1,13 +1,119 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #[macro_use] -extern crate relm4; +extern crate relm4_macros; -mod app; mod icons; +mod pa; +mod task; -use relm4::RelmApp; +use gtk4::{ + glib::{self, clone}, + prelude::*, + Align, Box as GtkBox, Button, Image, Label, ListBox, Orientation, PositionType, Revealer, + RevealerTransitionType, Scale, SelectionMode, Separator, Window, +}; +use once_cell::sync::Lazy; +use pulsectl::Handler; +use tokio::runtime::Runtime; + +static RT: Lazy = Lazy::new(|| Runtime::new().expect("failed to build tokio runtime")); fn main() { - RelmApp::::new("com.system76.cosmic.applets.audio").run(()); + let handler = + Handler::connect("com.system76.cosmic.applets.audio").expect("failed to connect to pulse"); + task::spawn_local(clone!(@strong handler.mainloop as main_loop => async move { + pa::drive_main_loop(main_loop).await + })); + view! { + window = Window { + set_title: Some("COSMIC Network Applet"), + set_default_width: 400, + set_default_height: 300, + + set_child: window_box = Some(&GtkBox) { + set_orientation: Orientation::Vertical, + set_spacing: 24, + append: output_box = &GtkBox { + set_orientation: Orientation::Horizontal, + set_spacing: 16, + append: output_icon = &Image { + set_icon_name: Some("audio-speakers-symbolic"), + }, + append: output_volume = &Scale::with_range(Orientation::Horizontal, 0., 100., 1.) { + set_format_value_func: |_, value| { + format!("{:.0}%", value) + }, + //set_value: watch! { model.default_output.as_ref().map(|info| (info.volume.avg().0 as f64 / Volume::NORMAL.0 as f64) * 100.).unwrap_or(0.) }, + set_value_pos: PositionType::Right, + set_hexpand: true + } + }, + append: input_box = &GtkBox { + set_orientation: Orientation::Horizontal, + set_spacing: 16, + append: input_icon = &Image { + set_icon_name: Some("audio-input-microphone-symbolic"), + }, + append: input_volume = &Scale::with_range(Orientation::Horizontal, 0., 100., 1.) { + set_format_value_func: |_, value| { + format!("{:.0}%", value) + }, + /*set_value: watch! { + model.default_input + .as_ref() + .map(|info| (info.volume.avg().0 as f64 / Volume::NORMAL.0 as f64) * 100.) + .unwrap_or(0.) + },*/ + set_value_pos: PositionType::Right, + set_hexpand: true + } + }, + append: _sep = &Separator { + set_orientation: Orientation::Horizontal, + }, + append: output_list_box = &GtkBox { + set_orientation: Orientation::Vertical, + append: current_output_button = &Button { + set_child: current_output = Some(&Label) {}, + connect_clicked(outputs_revealer) => move |_| { + outputs_revealer.set_reveal_child(!outputs_revealer.reveals_child()); + } + }, + append: outputs_revealer = &Revealer { + set_transition_type: RevealerTransitionType::SlideDown, + set_child: outputs = Some(&ListBox) { + set_selection_mode: SelectionMode::None, + set_activate_on_single_click: true + } + } + }, + append: _sep = &Separator { + set_orientation: Orientation::Horizontal, + }, + append: input_list_box = &GtkBox { + set_orientation: Orientation::Vertical, + append: current_input_button = &Button { + set_child: current_input = Some(&Label) {}, + connect_clicked(inputs_revealer) => move |_| { + inputs_revealer.set_reveal_child(!inputs_revealer.reveals_child()); + } + }, + append: inputs_revealer = &Revealer { + set_transition_type: RevealerTransitionType::SlideDown, + set_child: inputs = Some(&ListBox) { + set_selection_mode: SelectionMode::None, + set_activate_on_single_click: true + } + } + }, + append: _sep = &Separator { + set_orientation: Orientation::Horizontal, + }, + append: playing_apps = &ListBox { + set_selection_mode: SelectionMode::None, + } + } + } + } } diff --git a/applets/cosmic-applet-audio/src/pa.rs b/applets/cosmic-applet-audio/src/pa.rs new file mode 100644 index 00000000..9a4b1d58 --- /dev/null +++ b/applets/cosmic-applet-audio/src/pa.rs @@ -0,0 +1,12 @@ +use async_io::Timer; +use futures_util::StreamExt; +use libpulse_binding::mainloop::standard::Mainloop; +use std::{cell::RefCell, rc::Rc, time::Duration}; + +pub async fn drive_main_loop(main_loop: Rc>) { + let mut timer = Timer::interval(Duration::from_millis(100)); + loop { + main_loop.borrow_mut().iterate(false); + timer.next().await; + } +} diff --git a/applets/cosmic-applet-audio/src/task.rs b/applets/cosmic-applet-audio/src/task.rs new file mode 100644 index 00000000..39fc9073 --- /dev/null +++ b/applets/cosmic-applet-audio/src/task.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +use std::future::Future; +use tokio::sync::oneshot; + +pub fn spawn(future: F) -> tokio::task::JoinHandle +where + F: Future + Send + 'static, + O: Send + 'static, +{ + crate::RT.spawn(future) +} + +pub fn block_on(future: F) -> O +where + F: Future + Send + 'static, + O: Send + 'static, +{ + crate::RT.block_on(future) +} + +pub fn spawn_local + 'static>(future: F) { + gtk4::glib::MainContext::default().spawn_local(future); +} + +pub async fn wait_for_local(future: F) -> Option +where + O: Send + 'static, + F: Future + Send + 'static, +{ + let (tx, rx) = oneshot::channel::(); + gtk4::glib::MainContext::default().spawn_local(async move { + std::mem::drop(tx.send(future.await)); + }); + rx.await.ok() +}