diff --git a/Cargo.lock b/Cargo.lock index 30fa82d1..9f3efacc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,26 +309,6 @@ dependencies = [ "xdg", ] -[[package]] -name = "cosmic-applet-audio" -version = "0.1.0" -dependencies = [ - "async-io", - "freedesktop-desktop-entry", - "futures", - "futures-util", - "gtk4", - "libcosmic-widgets", - "libpulse-binding", - "libpulse-glib-binding", - "mpris2-zbus", - "once_cell", - "relm4-macros 0.4.4", - "tokio", - "tracker", - "zbus", -] - [[package]] name = "cosmic-applet-graphics" version = "0.1.0" @@ -610,15 +590,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "dirs" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" -dependencies = [ - "dirs-sys", -] - [[package]] name = "dirs" version = "4.0.0" @@ -815,19 +786,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9d758e60b45e8d749c89c1b389ad8aee550f86aa12e2b9298b546dda7a82ab1" -[[package]] -name = "freedesktop-desktop-entry" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45157175a725e81f3f594382430b6b78af5f8f72db9bd51b94f0785f80fc6d29" -dependencies = [ - "dirs 3.0.2", - "gettext-rs", - "memchr", - "thiserror", - "xdg", -] - [[package]] name = "fsevent-sys" version = "4.1.0" @@ -1058,26 +1016,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gettext-rs" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e49ea8a8fad198aaa1f9655a2524b64b70eb06b2f3ff37da407566c93054f364" -dependencies = [ - "gettext-sys", - "locale_config", -] - -[[package]] -name = "gettext-sys" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c63ce2e00f56a206778276704bbe38564c8695249fdc8f354b4ef71c57c3839d" -dependencies = [ - "cc", - "temp-dir", -] - [[package]] name = "gio" version = "0.15.11" @@ -1528,56 +1466,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "libpulse-binding" -version = "2.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17be42160017e0ae993c03bfdab4ecb6f82ce3f8d515bd8da8fdf18d10703663" -dependencies = [ - "bitflags", - "libc", - "libpulse-sys", - "num-derive", - "num-traits", - "winapi", -] - -[[package]] -name = "libpulse-glib-binding" -version = "2.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0e7a964c9f7e95d4f073affc19adfda009fa0d55e8831dbb66c78be1d0e6e5" -dependencies = [ - "glib", - "glib-sys", - "libpulse-binding", - "libpulse-mainloop-glib-sys", -] - -[[package]] -name = "libpulse-mainloop-glib-sys" -version = "1.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f61c4064926cc77ea14bb206a21ce1d5a06e175e5c0ce078804bb6c4527b28" -dependencies = [ - "glib-sys", - "libpulse-sys", - "pkg-config", -] - -[[package]] -name = "libpulse-sys" -version = "1.19.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "991e6bd0efe2a36e6534e136e7996925e4c1a8e35b7807fe533f2beffff27c30" -dependencies = [ - "libc", - "num-derive", - "num-traits", - "pkg-config", - "winapi", -] - [[package]] name = "locale_config" version = "0.3.0" @@ -1672,18 +1560,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "mpris2-zbus" -version = "0.1.0" -source = "git+https://github.com/pop-os/mpris2-zbus#bcc8481ea7ccfc08aa870f28272d9093db3b1ba9" -dependencies = [ - "serde", - "thiserror", - "time 0.3.9", - "zbus", - "zvariant", -] - [[package]] name = "nanorand" version = "0.7.0" @@ -1759,17 +1635,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "num-derive" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "num-integer" version = "0.1.45" @@ -2501,12 +2366,6 @@ dependencies = [ "version-compare", ] -[[package]] -name = "temp-dir" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af547b166dd1ea4b472165569fc456cfb6818116f854690b0ff205e636523dab" - [[package]] name = "tempfile" version = "3.3.0" @@ -2971,7 +2830,7 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6" dependencies = [ - "dirs 4.0.0", + "dirs", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c085b67b..02de8a98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = [ - "applets/cosmic-applet-audio", "applets/cosmic-applet-graphics", "applets/cosmic-applet-network", "applets/cosmic-applet-notifications", @@ -13,5 +12,6 @@ members = [ ] exclude = [ + "applets/cosmic-applet-audio", "applets/cosmic-applet-battery", ] diff --git a/applets/cosmic-applet-audio/Cargo.toml b/applets/cosmic-applet-audio/Cargo.toml index 993321a6..acdda3e1 100644 --- a/applets/cosmic-applet-audio/Cargo.toml +++ b/applets/cosmic-applet-audio/Cargo.toml @@ -7,7 +7,7 @@ license = "GPL-3.0-or-later" [dependencies] futures = "0.3.21" futures-util = "0.3.21" -libcosmic-widgets = { git = "https://github.com/pop-os/libcosmic" } +libcosmic-widgets = { git = "https://github.com/pop-os/libcosmic", branch = "relm4-next" } libpulse-binding = "2.26.0" libpulse-glib-binding = "2.25.0" tracker = "0.1.1" @@ -15,9 +15,10 @@ 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" +relm4 = { git = "https://github.com/relm4/relm4", branch = "next", features = ["macros"] } +relm4-macros = { git = "https://github.com/relm4/relm4", branch = "next" } once_cell = "1.10.0" -gtk4 = { version = "0.4.7", features = ["v4_2"] } +gtk4 = { git = "https://github.com/gtk-rs/gtk4-rs", features = ["v4_2"] } async-io = "1.6.0" [features] diff --git a/applets/cosmic-applet-audio/src/app.rs b/applets/cosmic-applet-audio/src/app.rs index 00188c65..988b45f4 100644 --- a/applets/cosmic-applet-audio/src/app.rs +++ b/applets/cosmic-applet-audio/src/app.rs @@ -2,17 +2,10 @@ use crate::icons::{parse_desktop_icons, DesktopApplication}; use futures_util::StreamExt; use libcosmic_widgets::LabeledItem; use libpulse_binding::{ - context::subscribe::{Facility, InterestMaskSet, Operation}, + context::{subscribe::{Facility, InterestMaskSet, Operation}, State}, volume::Volume, }; use mpris2_zbus::media_player::MediaPlayer; -use pulsectl::{ - controllers::{ - types::{ApplicationInfo, DeviceInfo}, - AppControl, DeviceControl, SinkController, SourceController, - }, - Handler, -}; use relm4::{ component, gtk::{ @@ -22,12 +15,14 @@ use relm4::{ Align, Box as GtkBox, Button, Image, Label, ListBox, Orientation, PositionType, Revealer, RevealerTransitionType, Scale, Separator, Window, }, - view, ComponentParts, RelmContainerExt, Sender, SimpleComponent, + view, ComponentParts, ComponentSender, RelmContainerExt, Sender, SimpleComponent, send, }; use std::{collections::HashMap, rc::Rc}; use tracker::track; use zbus::Connection; +use crate::pa::{DeviceInfo, PA}; + pub enum AppInput { Inputs, Outputs, @@ -59,7 +54,7 @@ pub struct App { #[do_not_track] desktop_icons: HashMap, #[do_not_track] - handler: Handler, + pa: PA, } impl Default for App { @@ -74,15 +69,8 @@ impl Default for App { let outputs = output_controller.list_devices().unwrap_or_default(); let now_playing = Vec::new(); let desktop_icons = parse_desktop_icons(); - let handler = Handler::connect("com.system76.cosmic.applets.audio") - .expect("failed to connect to pulse"); - relm4::spawn_local(clone!(@weak handler.mainloop as main_loop => async move { - let mut timer = async_io::Timer::interval(std::time::Duration::from_millis(100)); - loop { - main_loop.borrow_mut().iterate(false); - timer.next().await; - } - })); + // XXX handle no pulseaudio daemon? + let pa = PA::new().unwrap(); Self { default_input, inputs, @@ -90,7 +78,7 @@ impl Default for App { outputs, now_playing, desktop_icons, - handler, + pa, tracker: 0, } } @@ -142,23 +130,27 @@ impl App { } fn subscribe_for_updates(&self, input: &Sender) { - let mut context = self.handler.context.borrow_mut(); let input_clone = input.clone(); - context.set_subscribe_callback(Some(Box::new(move |facility, operation, _idx| { + self.pa.set_subscribe_callback(move |facility, operation, _idx| { if !matches!(operation, Some(Operation::Changed)) { return; } match facility { Some(Facility::Sink) => { - send!(input_clone, AppInput::OutputVolume); + input_clone.send(AppInput::OutputVolume); } Some(Facility::Source) => { - send!(input_clone, AppInput::InputVolume); + input_clone.send(AppInput::InputVolume); } _ => {} } - }))); - context.subscribe(InterestMaskSet::SINK | InterestMaskSet::SOURCE, |_| {}); + }); + self.pa.set_state_callback(move |pa, state| { + if state == State::Ready { + pa.subscribe(InterestMaskSet::SINK | InterestMaskSet::SOURCE); + } + }); + } fn refresh_input_list(&mut self) { let mut input_controller = @@ -277,7 +269,8 @@ impl App { append: media_buttons = &GtkBox { set_halign: Align::End, append: pause_button = &Button { - set_child: pause_button_img = Some(&Image) { + #[wrap(Some)] + set_child: pause_button_img = &Image { set_icon_name: Some("media-playback-pause-symbolic"), set_pixel_size: 24, }, @@ -309,89 +302,95 @@ impl SimpleComponent for App { set_default_width: 400, set_default_height: 300, - &GtkBox { + GtkBox { set_orientation: Orientation::Vertical, set_spacing: 24, - &GtkBox { + GtkBox { set_orientation: Orientation::Horizontal, set_spacing: 16, - &Image { + 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.) }, + #[watch] + set_value: 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 } }, - &GtkBox { + GtkBox { set_orientation: Orientation::Horizontal, set_spacing: 16, - &Image { + 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.) - }, + #[watch] + set_value: 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 } }, - &Separator { + Separator { set_orientation: Orientation::Horizontal, }, - &GtkBox { + GtkBox { set_orientation: Orientation::Vertical, - &Button { - set_child: current_output = Some(&Label) { - set_text: watch! { model.get_default_output_name() } + Button { + #[wrap(Some)] + set_child: current_output = &Label { + #[watch] + set_text: model.get_default_output_name() }, - connect_clicked(input, outputs_revealer) => move |_| { - send!(input, AppInput::Outputs); + connect_clicked[sender, outputs_revealer] => move |_| { + sender.input(AppInput::Outputs); outputs_revealer.set_reveal_child(!outputs_revealer.reveals_child()); } }, append: outputs_revealer = &Revealer { set_transition_type: RevealerTransitionType::SlideDown, - set_child: outputs = Some(&ListBox) { + #[wrap(Some)] + set_child: outputs = &ListBox { set_selection_mode: gtk::SelectionMode::None, set_activate_on_single_click: true } } }, - &Separator { + Separator { set_orientation: Orientation::Horizontal, }, - &GtkBox { + GtkBox { set_orientation: Orientation::Vertical, - &Button { - set_child: current_input = Some(&Label) { - set_text: watch! { model.get_default_input_name() } + Button { + #[wrap(Some)] + set_child: current_input = &Label { + #[watch] + set_text: model.get_default_input_name() }, - connect_clicked(input, inputs_revealer) => move |_| { - send!(input, AppInput::Inputs); + connect_clicked[sender, inputs_revealer] => move |_| { + sender.input(AppInput::Inputs); inputs_revealer.set_reveal_child(!inputs_revealer.reveals_child()); } }, append: inputs_revealer = &Revealer { set_transition_type: RevealerTransitionType::SlideDown, - set_child: inputs = Some(&ListBox) { + #[wrap(Some)] + set_child: inputs = &ListBox { set_selection_mode: gtk::SelectionMode::None, set_activate_on_single_click: true } } }, - &Separator { + Separator { set_orientation: Orientation::Horizontal, }, append: playing_apps = &ListBox { @@ -401,15 +400,14 @@ impl SimpleComponent for App { } } - fn init_parts( + fn init( _init_params: Self::InitParams, root: &Self::Root, - input: &Sender, - _output: &Sender, + sender: &ComponentSender, ) -> ComponentParts { let model = App::default(); let widgets = view_output!(); - model.subscribe_for_updates(input); + model.subscribe_for_updates(&sender.input); ComponentParts { model, widgets } } @@ -417,8 +415,7 @@ impl SimpleComponent for App { fn update( &mut self, msg: Self::Input, - _input: &Sender, - _output: &Sender, + _sender: &ComponentSender, ) { self.reset(); match msg { diff --git a/applets/cosmic-applet-audio/src/main.rs b/applets/cosmic-applet-audio/src/main.rs index 41d7ca43..4c4f7fe0 100644 --- a/applets/cosmic-applet-audio/src/main.rs +++ b/applets/cosmic-applet-audio/src/main.rs @@ -82,7 +82,8 @@ fn app(application: &Application) { set_default_width: 400, set_default_height: 300, - set_child: window_box = Some(&GtkBox) { + #[wrap(Some)] + set_child: window_box = &GtkBox { set_orientation: Orientation::Vertical, set_spacing: 24, append: output_box = &GtkBox { @@ -119,14 +120,16 @@ fn app(application: &Application) { 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 |_| { + #[wrap(Some)] + set_child: current_output = &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) { + #[wrap(Some)] + set_child: outputs = &ListBox { set_selection_mode: SelectionMode::None, set_activate_on_single_click: true } @@ -138,14 +141,16 @@ fn app(application: &Application) { 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 |_| { + #[wrap(Some)] + set_child: current_input = &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) { + #[wrap(Some)] + set_child: inputs = &ListBox { set_selection_mode: SelectionMode::None, set_activate_on_single_click: true } diff --git a/applets/cosmic-applet-audio/src/pa.rs b/applets/cosmic-applet-audio/src/pa.rs index bef28b7e..093ef39e 100644 --- a/applets/cosmic-applet-audio/src/pa.rs +++ b/applets/cosmic-applet-audio/src/pa.rs @@ -19,6 +19,7 @@ pub struct DeviceInfo { pub name: Option, pub description: Option, pub volume: ChannelVolumes, + pub index: u32, } pub struct ServerInfo { @@ -104,6 +105,7 @@ impl PA { name: item.name.clone().map(|x| x.into_owned()), description: item.description.clone().map(|x| x.into_owned()), volume: item.volume, + index: item.index, }), ListResult::End => { sender.take().unwrap().send(Ok(items.take().unwrap())); @@ -132,6 +134,7 @@ impl PA { name: item.name.clone().map(|x| x.into_owned()), description: item.description.clone().map(|x| x.into_owned()), volume: item.volume, + index: item.index, }); } ListResult::End => { @@ -163,6 +166,7 @@ impl PA { name: item.name.clone().map(|x| x.into_owned()), description: item.description.clone().map(|x| x.into_owned()), volume: item.volume, + index: item.index, }), ListResult::End => { sender.take().unwrap().send(Ok(items.take().unwrap())); @@ -191,6 +195,7 @@ impl PA { name: item.name.clone().map(|x| x.into_owned()), description: item.description.clone().map(|x| x.into_owned()), volume: item.volume, + index: item.index, }); } ListResult::End => { diff --git a/debian/rules b/debian/rules index aaa756a9..11deba6b 100755 --- a/debian/rules +++ b/debian/rules @@ -13,12 +13,13 @@ override_dh_shlibdeps: override_dh_auto_clean: if test "${CLEAN}" = "1"; then \ cargo clean; \ + cargo clean --manifest-path applets/cosmic-applet-audio/Cargo.toml; \ cargo clean --manifest-path applets/cosmic-applet-battery/Cargo.toml; \ fi if ! ischroot && test "${VENDOR}" = "1"; then \ mkdir -p .cargo; \ - cargo vendor --sync Cargo.toml --sync applets/cosmic-applet-battery/Cargo.toml | head -n -1 > .cargo/config; \ + cargo vendor --sync Cargo.toml --sync applets/cosmic-applet-audio/Cargo.toml applets/cosmic-applet-battery/Cargo.toml | head -n -1 > .cargo/config; \ echo 'directory = "vendor"' >> .cargo/config; \ tar pcf vendor.tar vendor; \ rm -rf vendor; \ diff --git a/justfile b/justfile index ee45c06b..3d147263 100644 --- a/justfile +++ b/justfile @@ -28,6 +28,7 @@ workspaces_button_id := 'com.system76.CosmicPanelWorkspacesButton' all: _extract_vendor cargo build {{cargo_args}} + cargo build --manifest-path applets/cosmic-applet-audio/Cargo.toml {{cargo_args}} cargo build --manifest-path applets/cosmic-applet-battery/Cargo.toml {{cargo_args}} # Installs files into the system @@ -42,7 +43,7 @@ install: # audio install -Dm0644 applets/cosmic-applet-audio/data/icons/{{audio_id}}.svg {{iconsdir}}/{{audio_id}}.svg install -Dm0644 applets/cosmic-applet-audio/data/{{audio_id}}.desktop {{sharedir}}/applications/{{audio_id}}.desktop - install -Dm0755 target/release/cosmic-applet-audio {{bindir}}/cosmic-applet-audio + install -Dm0755 applets/cosmic-applet-audio/target/release/cosmic-applet-audio {{bindir}}/cosmic-applet-audio # battery install -Dm0644 applets/cosmic-applet-battery/data/icons/{{battery_id}}.svg {{iconsdir}}/{{battery_id}}.svg