mirror of
https://gitlab.freedesktop.org/pipewire/helvum.git
synced 2026-05-20 00:08:09 +02:00
deps: Update dependencies and remove once_cell dependency
- Change Rust edition to 2024 - Update gnome runtime to gnome 50 - Update pipewire from 0.7.1 to 0.9.2 - Update adwaita from 0.5 to 0.9.1 - Update glib from 0.18 to 0.22.7 - Replace once_cell dependency with std::cell::OnceCell
This commit is contained in:
parent
eb3b3cf298
commit
cec5cdb6ae
16 changed files with 586 additions and 603 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,3 +3,4 @@
|
|||
/.vscode
|
||||
/_build
|
||||
/target
|
||||
/repo
|
||||
|
|
|
|||
658
Cargo.lock
generated
658
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
22
Cargo.toml
22
Cargo.toml
|
|
@ -1,9 +1,11 @@
|
|||
[package]
|
||||
name = "helvum"
|
||||
version = "0.5.1"
|
||||
authors = ["Tom Wagner <tom.a.wagner@protonmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
authors = [
|
||||
"Tom Wagner <tom.a.wagner@protonmail.com>",
|
||||
"Jaŭhien Lavonćjeŭ <jauhien.lavoncjeu@gmail.com>"
|
||||
]
|
||||
edition = "2024"
|
||||
license = "GPL-3.0-only"
|
||||
description = "A GTK patchbay for pipewire"
|
||||
repository = "https://gitlab.freedesktop.org/pipewire/helvum"
|
||||
|
|
@ -14,12 +16,10 @@ categories = ["gui", "multimedia"]
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
pipewire = "0.7.1"
|
||||
adw = { version = "0.5", package = "libadwaita", features = ["v1_4"] }
|
||||
glib = { version = "0.18", features = ["log"] }
|
||||
|
||||
log = "0.4.11"
|
||||
|
||||
once_cell = "1.7.2"
|
||||
|
||||
pipewire = "0.9.2"
|
||||
adw = { version = "0.9.1", package = "libadwaita", features = ["v1_9"] }
|
||||
gtk = { package = "gtk4", version = "0.11.2", features = ["gnome_50"] }
|
||||
glib = { version = "0.22.7", features = ["log"] }
|
||||
futures = "0.3.32"
|
||||
log = "0.4.29"
|
||||
libc = "0.2"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"id": "org.pipewire.Helvum",
|
||||
"runtime": "org.gnome.Platform",
|
||||
"runtime-version": "45",
|
||||
"runtime-version": "50",
|
||||
"sdk": "org.gnome.Sdk",
|
||||
"sdk-extensions": [
|
||||
"org.freedesktop.Sdk.Extension.rust-stable",
|
||||
"org.freedesktop.Sdk.Extension.llvm16"
|
||||
"org.freedesktop.Sdk.Extension.llvm22"
|
||||
],
|
||||
"command": "helvum",
|
||||
"finish-args": [
|
||||
|
|
@ -16,8 +16,8 @@
|
|||
"--filesystem=xdg-run/pipewire-0"
|
||||
],
|
||||
"build-options": {
|
||||
"append-path": "/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm16/bin",
|
||||
"prepend-ld-library-path": "/usr/lib/sdk/llvm16/lib",
|
||||
"append-path": "/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm22/bin",
|
||||
"prepend-ld-library-path": "/usr/lib/sdk/llvm22/lib",
|
||||
"build-args": [
|
||||
"--share=network"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ gnome = import('gnome')
|
|||
base_id = 'org.pipewire.Helvum'
|
||||
|
||||
dependency('glib-2.0', version: '>= 2.66')
|
||||
dependency('gtk4', version: '>= 4.4.0')
|
||||
dependency('libadwaita-1', version: '>= 1.4')
|
||||
dependency('gtk4', version: '>= 4.22.0')
|
||||
dependency('libadwaita-1', version: '>= 1.9')
|
||||
dependency('libpipewire-0.3')
|
||||
|
||||
desktop_file_validate = find_program('desktop-file-validate', required: false)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
|
||||
// Copyright 2026 Jaŭhien Lavonćjeŭ <jauhien.lavoncjeu@gmail.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 3 as published by
|
||||
|
|
@ -14,14 +15,17 @@
|
|||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::cell::OnceCell;
|
||||
use futures::channel::mpsc::UnboundedReceiver;
|
||||
use pipewire::channel::Sender;
|
||||
|
||||
use adw::{
|
||||
gio,
|
||||
glib::{self, clone, Receiver},
|
||||
glib::{self, clone},
|
||||
gtk,
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use pipewire::channel::Sender;
|
||||
|
||||
use crate::{graph_manager::GraphManager, ui, GtkMessage, PipewireMessage};
|
||||
|
||||
|
|
@ -33,9 +37,6 @@ static AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
|
|||
mod imp {
|
||||
use super::*;
|
||||
|
||||
use adw::subclass::prelude::AdwApplicationImpl;
|
||||
use once_cell::unsync::OnceCell;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Application {
|
||||
pub(super) window: ui::Window,
|
||||
|
|
@ -60,7 +61,7 @@ mod imp {
|
|||
|
||||
let zoom_set_action =
|
||||
gio::SimpleAction::new("set-zoom", Some(&f64::static_variant_type()));
|
||||
zoom_set_action.connect_activate(clone!(@weak graphview => move|_, param| {
|
||||
zoom_set_action.connect_activate(clone!(#[weak] graphview, move|_, param| {
|
||||
let zoom_factor = param.unwrap().get::<f64>().unwrap();
|
||||
graphview.set_zoom_factor(zoom_factor, None)
|
||||
}));
|
||||
|
|
@ -97,7 +98,7 @@ mod imp {
|
|||
|
||||
// Add <Control-Q> shortcut for quitting the application.
|
||||
let quit = gtk::gio::SimpleAction::new("quit", None);
|
||||
quit.connect_activate(clone!(@weak obj => move |_, _| {
|
||||
quit.connect_activate(clone!(#[weak] obj, move |_, _| {
|
||||
obj.quit();
|
||||
}));
|
||||
obj.set_accels_for_action("app.quit", &["<Control>Q"]);
|
||||
|
|
@ -116,8 +117,7 @@ mod imp {
|
|||
let window = obj.active_window().unwrap();
|
||||
let authors: Vec<&str> = AUTHORS.split(':').collect();
|
||||
|
||||
let about_window = adw::AboutWindow::builder()
|
||||
.transient_for(&window)
|
||||
let about_dialog = adw::AboutDialog::builder()
|
||||
.application_icon(APP_ID)
|
||||
.application_name("Helvum")
|
||||
.developer_name("Tom Wagner")
|
||||
|
|
@ -128,7 +128,7 @@ mod imp {
|
|||
.license_type(gtk::License::Gpl30Only)
|
||||
.build();
|
||||
|
||||
about_window.present();
|
||||
about_dialog.present(Some(&window));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@ impl Application {
|
|||
/// Create the view.
|
||||
/// This will set up the entire user interface and prepare it for being run.
|
||||
pub(super) fn new(
|
||||
gtk_receiver: Receiver<PipewireMessage>,
|
||||
gtk_receiver: UnboundedReceiver<PipewireMessage>,
|
||||
pw_sender: Sender<GtkMessage>,
|
||||
) -> Self {
|
||||
let app: Application = glib::Object::builder()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
|
||||
// Copyright 2026 Jaŭhien Lavonćjeŭ <jauhien.lavoncjeu@gmail.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 3 as published by
|
||||
|
|
@ -14,31 +15,34 @@
|
|||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use adw::{glib, prelude::*, subclass::prelude::*};
|
||||
use std::{cell::{RefCell, OnceCell}, collections::HashMap};
|
||||
use futures::{prelude::*, channel::mpsc::UnboundedReceiver};
|
||||
|
||||
use pipewire::channel::Sender as PwSender;
|
||||
use adw::{glib::clone, prelude::*, subclass::prelude::*};
|
||||
|
||||
use crate::{ui::graph::GraphView, GtkMessage, PipewireMessage};
|
||||
use pipewire::{
|
||||
spa::{
|
||||
param::format::MediaType,
|
||||
utils::Direction
|
||||
},
|
||||
channel::Sender
|
||||
};
|
||||
|
||||
use crate::{ui::graph, ui::graph::GraphView, GtkMessage, PipewireMessage, NodeType};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
|
||||
use once_cell::unsync::OnceCell;
|
||||
|
||||
use crate::{ui::graph, MediaType, NodeType};
|
||||
|
||||
#[derive(Default, glib::Properties)]
|
||||
#[properties(wrapper_type = super::GraphManager)]
|
||||
pub struct GraphManager {
|
||||
#[property(get, set, construct_only)]
|
||||
pub graph: OnceCell<crate::ui::graph::GraphView>,
|
||||
pub graph: OnceCell<GraphView>,
|
||||
|
||||
#[property(get, set, construct_only)]
|
||||
pub connection_banner: OnceCell<adw::Banner>,
|
||||
|
||||
pub pw_sender: OnceCell<PwSender<crate::GtkMessage>>,
|
||||
pub pw_sender: OnceCell<Sender<crate::GtkMessage>>,
|
||||
pub items: RefCell<HashMap<u32, glib::Object>>,
|
||||
}
|
||||
|
||||
|
|
@ -53,10 +57,9 @@ mod imp {
|
|||
impl ObjectImpl for GraphManager {}
|
||||
|
||||
impl GraphManager {
|
||||
pub fn attach_receiver(&self, receiver: glib::Receiver<crate::PipewireMessage>) {
|
||||
receiver.attach(None, glib::clone!(
|
||||
@weak self as imp => @default-return glib::ControlFlow::Continue,
|
||||
move |msg| {
|
||||
pub fn attach_receiver(&self, mut receiver: UnboundedReceiver<PipewireMessage>) {
|
||||
glib::MainContext::default().spawn_local(clone!(#[weak(rename_to = imp)] self, async move {
|
||||
while let Some(msg) = receiver.next().await {
|
||||
match msg {
|
||||
PipewireMessage::NodeAdded { id, name, node_type } => imp.add_node(id, name.as_str(), node_type),
|
||||
PipewireMessage::NodeNameChanged { id, name, media_name } => imp.node_name_changed(id, &name, &media_name),
|
||||
|
|
@ -80,9 +83,8 @@ mod imp {
|
|||
imp.clear();
|
||||
},
|
||||
};
|
||||
glib::ControlFlow::Continue
|
||||
}
|
||||
));
|
||||
}));
|
||||
}
|
||||
|
||||
/// Add a new node to the view.
|
||||
|
|
@ -130,7 +132,7 @@ mod imp {
|
|||
}
|
||||
|
||||
/// Add a new port to the view.
|
||||
fn add_port(&self, id: u32, name: &str, node_id: u32, direction: pipewire::spa::Direction) {
|
||||
fn add_port(&self, id: u32, name: &str, node_id: u32, direction: Direction) {
|
||||
log::info!("Adding port to graph: id {}", id);
|
||||
|
||||
let mut items = self.items.borrow_mut();
|
||||
|
|
@ -150,7 +152,7 @@ mod imp {
|
|||
port.connect_local(
|
||||
"port_toggled",
|
||||
false,
|
||||
glib::clone!(@weak self as app => @default-return None, move |args| {
|
||||
glib::clone!(#[weak(rename_to = app)] self, #[upgrade_or] None, move |args| {
|
||||
// Args always look like this: &[widget, id_port_from, id_port_to]
|
||||
let port_from = args[1].get::<u32>().unwrap();
|
||||
let port_to = args[2].get::<u32>().unwrap();
|
||||
|
|
@ -273,7 +275,7 @@ mod imp {
|
|||
link.set_active(active);
|
||||
}
|
||||
|
||||
fn link_format_changed(&self, id: u32, media_type: pipewire::spa::format::MediaType) {
|
||||
fn link_format_changed(&self, id: u32, media_type: MediaType) {
|
||||
let items = self.items.borrow();
|
||||
|
||||
let Some(link) = items.get(&id) else {
|
||||
|
|
@ -326,8 +328,8 @@ impl GraphManager {
|
|||
pub fn new(
|
||||
graph: &GraphView,
|
||||
connection_banner: &adw::Banner,
|
||||
sender: PwSender<GtkMessage>,
|
||||
receiver: glib::Receiver<PipewireMessage>,
|
||||
sender: Sender<GtkMessage>,
|
||||
receiver: UnboundedReceiver<PipewireMessage>,
|
||||
) -> Self {
|
||||
let res: Self = glib::Object::builder()
|
||||
.property("graph", graph)
|
||||
|
|
|
|||
10
src/main.rs
10
src/main.rs
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
|
||||
// Copyright 2026 Jaŭhien Lavonćjeŭ <jauhien.lavoncjeu@gmail.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 3 as published by
|
||||
|
|
@ -19,8 +20,10 @@ mod graph_manager;
|
|||
mod pipewire_connection;
|
||||
mod ui;
|
||||
|
||||
use futures::channel::mpsc::unbounded;
|
||||
use pipewire::spa::{utils::Direction, param::format::MediaType};
|
||||
|
||||
use adw::{gtk, prelude::*};
|
||||
use pipewire::spa::{format::MediaType, Direction};
|
||||
|
||||
/// Messages sent by the GTK thread to notify the pipewire thread.
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -120,10 +123,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
// Start the pipewire thread with channels in both directions.
|
||||
|
||||
let (gtk_sender, gtk_receiver) = glib::MainContext::channel(glib::Priority::DEFAULT);
|
||||
let (gtk_sender, gtk_receiver) = unbounded::<PipewireMessage>();
|
||||
let (pw_sender, pw_receiver) = pipewire::channel::channel();
|
||||
let pw_thread =
|
||||
std::thread::spawn(move || pipewire_connection::thread_main(gtk_sender, pw_receiver));
|
||||
let pw_thread = std::thread::spawn(move || pipewire_connection::thread_main(gtk_sender, pw_receiver));
|
||||
|
||||
let app = application::Application::new(gtk_receiver, pw_sender.clone());
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
|
||||
// Copyright 2026 Jaŭhien Lavonćjeŭ <jauhien.lavoncjeu@gmail.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 3 as published by
|
||||
|
|
@ -23,22 +24,22 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
|
||||
use adw::glib::{self, clone};
|
||||
use futures::channel::mpsc::UnboundedSender;
|
||||
use glib::clone;
|
||||
use log::{debug, error, info, warn};
|
||||
use pipewire::{
|
||||
context::ContextRc,
|
||||
channel::Receiver,
|
||||
core::{CoreRc, PW_ID_CORE},
|
||||
keys,
|
||||
link::{Link, LinkChangeMask, LinkInfo, LinkListener, LinkState},
|
||||
node::{Node, NodeInfo, NodeListener},
|
||||
port::{Port, PortChangeMask, PortInfo, PortListener},
|
||||
prelude::*,
|
||||
properties,
|
||||
registry::{GlobalObject, Registry},
|
||||
spa::{
|
||||
param::{ParamInfoFlags, ParamType},
|
||||
ForeignDict, SpaResult,
|
||||
},
|
||||
types::ObjectType,
|
||||
Context, Core, MainLoop,
|
||||
link::{Link, LinkChangeMask, LinkInfoRef, LinkListener, LinkState},
|
||||
main_loop::{MainLoopRc},
|
||||
node::{Node, NodeInfoRef, NodeListener},
|
||||
port::{Port, PortChangeMask, PortInfoRef, PortListener},
|
||||
properties::properties,
|
||||
registry::{GlobalObject, RegistryRc},
|
||||
spa::{param::{ParamInfoFlags, ParamType}, utils::{dict::DictRef, result::SpaResult}},
|
||||
types::ObjectType
|
||||
};
|
||||
|
||||
use crate::{GtkMessage, MediaType, NodeType, PipewireMessage};
|
||||
|
|
@ -61,70 +62,63 @@ enum ProxyItem {
|
|||
|
||||
/// The "main" function of the pipewire thread.
|
||||
pub(super) fn thread_main(
|
||||
gtk_sender: glib::Sender<PipewireMessage>,
|
||||
mut pw_receiver: pipewire::channel::Receiver<GtkMessage>,
|
||||
gtk_sender: UnboundedSender<PipewireMessage>,
|
||||
mut pw_receiver: Receiver<GtkMessage>,
|
||||
) {
|
||||
let mainloop = MainLoop::new().expect("Failed to create mainloop");
|
||||
let context = Rc::new(Context::new(&mainloop).expect("Failed to create context"));
|
||||
let mainloop = MainLoopRc::new(None).expect("Failed to create mainloop");
|
||||
let context = ContextRc::new(&mainloop, None).expect("Failed to create context");
|
||||
let is_stopped = Rc::new(Cell::new(false));
|
||||
let mut is_connecting = false;
|
||||
|
||||
while !is_stopped.get() {
|
||||
// Try to connect
|
||||
let core = match context.connect(Some(properties! {
|
||||
let Ok(core) = context.connect_rc(Some(properties! {
|
||||
"media.category" => "Manager"
|
||||
})) {
|
||||
Ok(core) => Rc::new(core),
|
||||
Err(_) => {
|
||||
if !is_connecting {
|
||||
is_connecting = true;
|
||||
gtk_sender
|
||||
.send(PipewireMessage::Connecting)
|
||||
.expect("Failed to send message");
|
||||
}
|
||||
|
||||
// If connection is failed, try to connect again in 200ms
|
||||
let interval = Some(Duration::from_millis(200));
|
||||
|
||||
let timer = mainloop.add_timer(clone!(@strong mainloop => move |_| {
|
||||
mainloop.quit();
|
||||
}));
|
||||
|
||||
timer.update_timer(interval, None).into_result().unwrap();
|
||||
|
||||
let receiver = pw_receiver.attach(&mainloop, {
|
||||
clone!(@strong mainloop, @strong is_stopped => move |msg|
|
||||
if let GtkMessage::Terminate = msg {
|
||||
// main thread requested stop
|
||||
is_stopped.set(true);
|
||||
mainloop.quit();
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
mainloop.run();
|
||||
pw_receiver = receiver.deattach();
|
||||
|
||||
continue;
|
||||
})) else {
|
||||
if !is_connecting {
|
||||
is_connecting = true;
|
||||
gtk_sender.unbounded_send(PipewireMessage::Connecting).expect("Failed to send message");
|
||||
}
|
||||
|
||||
// If connection is failed, try to connect again in 200ms
|
||||
let interval = Some(Duration::from_millis(200));
|
||||
|
||||
let timer = mainloop.loop_().add_timer(clone!(#[strong] mainloop, move |_| {
|
||||
mainloop.quit();
|
||||
}));
|
||||
|
||||
timer.update_timer(interval, None).into_result().unwrap();
|
||||
|
||||
let receiver = pw_receiver.attach(&mainloop.loop_(), {
|
||||
clone!(#[strong] mainloop, #[strong] is_stopped, move |msg|
|
||||
if let GtkMessage::Terminate = msg {
|
||||
// main thread requested stop
|
||||
is_stopped.set(true);
|
||||
mainloop.quit();
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
mainloop.run();
|
||||
pw_receiver = receiver.deattach();
|
||||
|
||||
continue;
|
||||
};
|
||||
|
||||
if is_connecting {
|
||||
is_connecting = false;
|
||||
gtk_sender
|
||||
.send(PipewireMessage::Connected)
|
||||
.expect("Failed to send message");
|
||||
gtk_sender.unbounded_send(PipewireMessage::Connected).expect("Failed to send message");
|
||||
}
|
||||
|
||||
let registry = Rc::new(core.get_registry().expect("Failed to get registry"));
|
||||
let registry = core.get_registry_rc().expect("Failed to get registry");
|
||||
|
||||
// Keep proxies and their listeners alive so that we can receive info events.
|
||||
let proxies = Rc::new(RefCell::new(HashMap::new()));
|
||||
let state = Rc::new(RefCell::new(State::new()));
|
||||
|
||||
let receiver = pw_receiver.attach(&mainloop, {
|
||||
clone!(@strong mainloop, @weak core, @weak registry, @strong state, @strong is_stopped => move |msg| match msg {
|
||||
GtkMessage::ToggleLink { port_from, port_to } => toggle_link(port_from, port_to, &core, ®istry, &state),
|
||||
let receiver = pw_receiver.attach(&mainloop.loop_(), {
|
||||
clone!(#[strong] mainloop, #[strong] core, #[strong] registry, #[strong] state, #[strong] is_stopped, move |msg| match msg {
|
||||
GtkMessage::ToggleLink { port_from, port_to } => toggle_link(port_from, port_to, &core, ®istry, &state.borrow()),
|
||||
GtkMessage::Terminate => {
|
||||
// main thread requested stop
|
||||
is_stopped.set(true);
|
||||
|
|
@ -135,14 +129,13 @@ pub(super) fn thread_main(
|
|||
|
||||
let gtk_sender = gtk_sender.clone();
|
||||
let _listener = core.add_listener_local()
|
||||
.error(clone!(@strong mainloop, @strong gtk_sender, @strong is_stopped => move |id, _seq, res, message| {
|
||||
if id != pipewire::PW_ID_CORE {
|
||||
.error(clone!(#[strong] mainloop, #[strong] gtk_sender, move |id, _seq, res, message| {
|
||||
if id != PW_ID_CORE {
|
||||
return;
|
||||
}
|
||||
|
||||
if res == -libc::EPIPE {
|
||||
gtk_sender.send(PipewireMessage::Disconnected)
|
||||
.expect("Failed to send message");
|
||||
gtk_sender.unbounded_send(PipewireMessage::Disconnected).expect("Failed to send message");
|
||||
mainloop.quit();
|
||||
} else {
|
||||
let serr = SpaResult::from_c(res).into_result().unwrap_err();
|
||||
|
|
@ -153,19 +146,19 @@ pub(super) fn thread_main(
|
|||
|
||||
let _listener = registry
|
||||
.add_listener_local()
|
||||
.global(clone!(@strong gtk_sender, @weak registry, @strong proxies, @strong state =>
|
||||
.global(clone!(#[strong] gtk_sender, #[strong] registry, #[strong] proxies, #[strong] state,
|
||||
move |global| match global.type_ {
|
||||
ObjectType::Node => handle_node(global, >k_sender, ®istry, &proxies, &state),
|
||||
ObjectType::Node => handle_node(global, >k_sender, ®istry, &proxies, &mut state.borrow_mut()),
|
||||
ObjectType::Port => handle_port(global, >k_sender, ®istry, &proxies, &state),
|
||||
ObjectType::Link => handle_link(global, >k_sender, ®istry, &proxies, &state),
|
||||
ObjectType::Link => handle_link(global, >k_sender, ®istry, &mut proxies.borrow_mut(), &state),
|
||||
_ => {
|
||||
// Other objects are not interesting to us
|
||||
}
|
||||
}
|
||||
))
|
||||
.global_remove(clone!(@strong proxies, @strong state => move |id| {
|
||||
.global_remove(clone!(#[strong] proxies, #[strong] state, move |id| {
|
||||
if let Some(item) = state.borrow_mut().remove(id) {
|
||||
gtk_sender.send(match item {
|
||||
gtk_sender.unbounded_send(match item {
|
||||
Item::Node { .. } => PipewireMessage::NodeRemoved {id},
|
||||
Item::Port { node_id } => PipewireMessage::PortRemoved {id, node_id},
|
||||
Item::Link { .. } => PipewireMessage::LinkRemoved {id},
|
||||
|
|
@ -187,21 +180,22 @@ pub(super) fn thread_main(
|
|||
}
|
||||
|
||||
/// Get the nicest possible name for the node, using a fallback chain of possible name attributes
|
||||
fn get_node_name(props: &ForeignDict) -> &str {
|
||||
fn get_node_name(props: &DictRef) -> String {
|
||||
props
|
||||
.get(&keys::NODE_DESCRIPTION)
|
||||
.or_else(|| props.get(&keys::NODE_NICK))
|
||||
.or_else(|| props.get(&keys::NODE_NAME))
|
||||
.unwrap_or_default()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Handle a new node being added
|
||||
fn handle_node(
|
||||
node: &GlobalObject<ForeignDict>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
registry: &Rc<Registry>,
|
||||
node: &GlobalObject<&DictRef>,
|
||||
sender: &UnboundedSender<PipewireMessage>,
|
||||
registry: &RegistryRc,
|
||||
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
|
||||
state: &Rc<RefCell<State>>,
|
||||
state: &mut State,
|
||||
) {
|
||||
let props = node
|
||||
.props
|
||||
|
|
@ -230,21 +224,19 @@ fn handle_node(
|
|||
})
|
||||
.or_else(|| props.get("media.class").and_then(media_class));
|
||||
|
||||
state.borrow_mut().insert(node.id, Item::Node);
|
||||
state.insert(node.id, Item::Node);
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::NodeAdded {
|
||||
id: node.id,
|
||||
name,
|
||||
node_type,
|
||||
})
|
||||
.expect("Failed to send message");
|
||||
sender.unbounded_send(PipewireMessage::NodeAdded {
|
||||
id: node.id,
|
||||
name,
|
||||
node_type,
|
||||
}).expect("Failed to send message");
|
||||
|
||||
let proxy: Node = registry.bind(node).expect("Failed to bind to node proxy");
|
||||
let listener = proxy
|
||||
.add_listener_local()
|
||||
.info(clone!(@strong sender, @strong proxies => move |info| {
|
||||
handle_node_info(info, &sender, &proxies);
|
||||
.info(clone!(#[strong] sender, #[strong] proxies, move |info| {
|
||||
handle_node_info(info, &sender, &proxies.borrow());
|
||||
}))
|
||||
.register();
|
||||
|
||||
|
|
@ -258,14 +250,13 @@ fn handle_node(
|
|||
}
|
||||
|
||||
fn handle_node_info(
|
||||
info: &NodeInfo,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
|
||||
info: &NodeInfoRef,
|
||||
sender: &UnboundedSender<PipewireMessage>,
|
||||
proxies: &HashMap<u32, ProxyItem>,
|
||||
) {
|
||||
debug!("Received node info: {:?}", info);
|
||||
|
||||
let id = info.id();
|
||||
let proxies = proxies.borrow();
|
||||
let Some(ProxyItem::Node { .. }) = proxies.get(&id) else {
|
||||
error!("Received info on unknown node with id {id}");
|
||||
return;
|
||||
|
|
@ -275,21 +266,19 @@ fn handle_node_info(
|
|||
if let Some(media_name) = props.get(&keys::MEDIA_NAME) {
|
||||
let name = get_node_name(props).to_string();
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::NodeNameChanged {
|
||||
id,
|
||||
name,
|
||||
media_name: media_name.to_string(),
|
||||
})
|
||||
.expect("Failed to send message");
|
||||
sender.unbounded_send(PipewireMessage::NodeNameChanged {
|
||||
id,
|
||||
name,
|
||||
media_name: media_name.to_string(),
|
||||
}).expect("Failed to send message");
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a new port being added
|
||||
fn handle_port(
|
||||
port: &GlobalObject<ForeignDict>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
registry: &Rc<Registry>,
|
||||
port: &GlobalObject<&DictRef>,
|
||||
sender: &UnboundedSender<PipewireMessage>,
|
||||
registry: &RegistryRc,
|
||||
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
|
||||
state: &Rc<RefCell<State>>,
|
||||
) {
|
||||
|
|
@ -298,11 +287,11 @@ fn handle_port(
|
|||
let listener = proxy
|
||||
.add_listener_local()
|
||||
.info(
|
||||
clone!(@strong proxies, @strong state, @strong sender => move |info| {
|
||||
handle_port_info(info, &proxies, &state, &sender);
|
||||
clone!(#[strong] proxies, #[strong] state, #[strong] sender, move |info| {
|
||||
handle_port_info(info, &proxies.borrow(), &mut state.borrow_mut(), &sender);
|
||||
}),
|
||||
)
|
||||
.param(clone!(@strong sender => move |_, param_id, _, _, param| {
|
||||
.param(clone!(#[strong] sender, move |_, param_id, _, _, param| {
|
||||
if param_id == ParamType::EnumFormat {
|
||||
handle_port_enum_format(port_id, param, &sender)
|
||||
}
|
||||
|
|
@ -319,22 +308,19 @@ fn handle_port(
|
|||
}
|
||||
|
||||
fn handle_port_info(
|
||||
info: &PortInfo,
|
||||
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
|
||||
state: &Rc<RefCell<State>>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
info: &PortInfoRef,
|
||||
proxies: &HashMap<u32, ProxyItem>,
|
||||
state: &mut State,
|
||||
sender: &UnboundedSender<PipewireMessage>,
|
||||
) {
|
||||
debug!("Received port info: {:?}", info);
|
||||
|
||||
let id = info.id();
|
||||
let proxies = proxies.borrow();
|
||||
let Some(ProxyItem::Port { proxy, .. }) = proxies.get(&id) else {
|
||||
log::error!("Received info on unknown port with id {id}");
|
||||
return;
|
||||
};
|
||||
|
||||
let mut state = state.borrow_mut();
|
||||
|
||||
if let Some(Item::Port { .. }) = state.get(id) {
|
||||
// Info was an update, figure out if we should notify the GTK thread
|
||||
if info.change_mask().contains(PortChangeMask::PARAMS) {
|
||||
|
|
@ -362,41 +348,37 @@ fn handle_port_info(
|
|||
}
|
||||
}
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::PortAdded {
|
||||
id,
|
||||
node_id,
|
||||
name,
|
||||
direction: info.direction(),
|
||||
})
|
||||
.expect("Failed to send message");
|
||||
sender.unbounded_send(PipewireMessage::PortAdded {
|
||||
id,
|
||||
node_id,
|
||||
name,
|
||||
direction: info.direction(),
|
||||
}).expect("Failed to send message");
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_port_enum_format(
|
||||
port_id: u32,
|
||||
param: Option<&pipewire::spa::pod::Pod>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
sender: &UnboundedSender<PipewireMessage>,
|
||||
) {
|
||||
let media_type = param
|
||||
.and_then(|param| pipewire::spa::param::format_utils::parse_format(param).ok())
|
||||
.map(|(media_type, _media_subtype)| media_type)
|
||||
.unwrap_or(MediaType::Unknown);
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::PortFormatChanged {
|
||||
id: port_id,
|
||||
media_type,
|
||||
})
|
||||
.expect("Failed to send message")
|
||||
sender.unbounded_send(PipewireMessage::PortFormatChanged {
|
||||
id: port_id,
|
||||
media_type,
|
||||
}).expect("Failed to send message");
|
||||
}
|
||||
|
||||
/// Handle a new link being added
|
||||
fn handle_link(
|
||||
link: &GlobalObject<ForeignDict>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
registry: &Rc<Registry>,
|
||||
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
|
||||
link: &GlobalObject<&DictRef>,
|
||||
sender: &UnboundedSender<PipewireMessage>,
|
||||
registry: &RegistryRc,
|
||||
proxies: &mut HashMap<u32, ProxyItem>,
|
||||
state: &Rc<RefCell<State>>,
|
||||
) {
|
||||
debug!(
|
||||
|
|
@ -407,12 +389,12 @@ fn handle_link(
|
|||
let proxy: Link = registry.bind(link).expect("Failed to bind to link proxy");
|
||||
let listener = proxy
|
||||
.add_listener_local()
|
||||
.info(clone!(@strong state, @strong sender => move |info| {
|
||||
handle_link_info(info, &state, &sender);
|
||||
.info(clone!(#[strong] state, #[strong] sender, move |info| {
|
||||
handle_link_info(info, &mut state.borrow_mut(), &sender);
|
||||
}))
|
||||
.register();
|
||||
|
||||
proxies.borrow_mut().insert(
|
||||
proxies.insert(
|
||||
link.id,
|
||||
ProxyItem::Link {
|
||||
_proxy: proxy,
|
||||
|
|
@ -422,32 +404,27 @@ fn handle_link(
|
|||
}
|
||||
|
||||
fn handle_link_info(
|
||||
info: &LinkInfo,
|
||||
state: &Rc<RefCell<State>>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
info: &LinkInfoRef,
|
||||
state: &mut State,
|
||||
sender: &UnboundedSender<PipewireMessage>,
|
||||
) {
|
||||
debug!("Received link info: {:?}", info);
|
||||
|
||||
let id = info.id();
|
||||
|
||||
let mut state = state.borrow_mut();
|
||||
if let Some(Item::Link { .. }) = state.get(id) {
|
||||
// Info was an update - figure out if we should notify the gtk thread
|
||||
if info.change_mask().contains(LinkChangeMask::STATE) {
|
||||
sender
|
||||
.send(PipewireMessage::LinkStateChanged {
|
||||
id,
|
||||
active: matches!(info.state(), LinkState::Active),
|
||||
})
|
||||
.expect("Failed to send message");
|
||||
sender.unbounded_send(PipewireMessage::LinkStateChanged {
|
||||
id,
|
||||
active: matches!(info.state(), LinkState::Active),
|
||||
}).expect("Failed to send message");
|
||||
}
|
||||
if info.change_mask().contains(LinkChangeMask::FORMAT) {
|
||||
sender
|
||||
.send(PipewireMessage::LinkFormatChanged {
|
||||
id,
|
||||
media_type: get_link_media_type(info),
|
||||
})
|
||||
.expect("Failed to send message");
|
||||
sender.unbounded_send(PipewireMessage::LinkFormatChanged {
|
||||
id,
|
||||
media_type: get_link_media_type(info),
|
||||
}).expect("Failed to send message");
|
||||
}
|
||||
} else {
|
||||
// First time we get info. We can now notify the gtk thread of a new link.
|
||||
|
|
@ -456,15 +433,13 @@ fn handle_link_info(
|
|||
|
||||
state.insert(id, Item::Link { port_from, port_to });
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::LinkAdded {
|
||||
id,
|
||||
port_from,
|
||||
port_to,
|
||||
active: matches!(info.state(), LinkState::Active),
|
||||
media_type: get_link_media_type(info),
|
||||
})
|
||||
.expect("Failed to send message");
|
||||
sender.unbounded_send(PipewireMessage::LinkAdded {
|
||||
id,
|
||||
port_from,
|
||||
port_to,
|
||||
active: matches!(info.state(), LinkState::Active),
|
||||
media_type: get_link_media_type(info),
|
||||
}).expect("Failed to send message");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -472,11 +447,10 @@ fn handle_link_info(
|
|||
fn toggle_link(
|
||||
port_from: u32,
|
||||
port_to: u32,
|
||||
core: &Rc<Core>,
|
||||
registry: &Rc<Registry>,
|
||||
state: &Rc<RefCell<State>>,
|
||||
core: &CoreRc,
|
||||
registry: &RegistryRc,
|
||||
state: &State,
|
||||
) {
|
||||
let state = state.borrow_mut();
|
||||
if let Some(id) = state.get_link_id(port_from, port_to) {
|
||||
info!("Requesting removal of link with id {}", id);
|
||||
|
||||
|
|
@ -495,7 +469,7 @@ fn toggle_link(
|
|||
.get_node_of_port(port_to)
|
||||
.expect("Requested port not in state");
|
||||
|
||||
if let Err(e) = core.create_object::<Link, _>(
|
||||
if let Err(e) = core.create_object::<Link>(
|
||||
"link-factory",
|
||||
&properties! {
|
||||
"link.output.node" => node_from.to_string(),
|
||||
|
|
@ -510,7 +484,7 @@ fn toggle_link(
|
|||
}
|
||||
}
|
||||
|
||||
fn get_link_media_type(link_info: &LinkInfo) -> MediaType {
|
||||
fn get_link_media_type(link_info: &LinkInfoRef) -> MediaType {
|
||||
let media_type = link_info
|
||||
.format()
|
||||
.and_then(|format| pipewire::spa::param::format_utils::parse_format(format).ok())
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
|
||||
// Copyright 2026 Jaŭhien Lavonćjeŭ <jauhien.lavoncjeu@gmail.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 3 as published by
|
||||
|
|
@ -14,19 +15,24 @@
|
|||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
sync::LazyLock,
|
||||
cell::{Cell, RefCell},
|
||||
collections::{HashMap, HashSet}
|
||||
};
|
||||
|
||||
use pipewire::spa::{utils::Direction, param::format::MediaType};
|
||||
|
||||
use gtk::{self, cairo, gsk, gdk, graphene::{self, Point}};
|
||||
use adw::{
|
||||
gio,
|
||||
glib::{self, clone},
|
||||
gtk::{
|
||||
self, cairo,
|
||||
graphene::{self, Point},
|
||||
gsk,
|
||||
},
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use log::warn;
|
||||
|
||||
use super::{Link, Node, Port};
|
||||
use crate::NodeType;
|
||||
|
|
@ -36,15 +42,6 @@ const CANVAS_SIZE: f64 = 5000.0;
|
|||
mod imp {
|
||||
use super::*;
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use adw::gtk::gdk::{self};
|
||||
use log::warn;
|
||||
use once_cell::sync::Lazy;
|
||||
use pipewire::spa::format::MediaType;
|
||||
use pipewire::spa::Direction;
|
||||
|
||||
pub struct Colors {
|
||||
audio: gdk::RGBA,
|
||||
video: gdk::RGBA,
|
||||
|
|
@ -151,7 +148,7 @@ mod imp {
|
|||
}
|
||||
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
static PROPERTIES: LazyLock<Vec<glib::ParamSpec>> = LazyLock::new(|| {
|
||||
vec![
|
||||
glib::ParamSpecOverride::for_interface::<gtk::Scrollable>("hadjustment"),
|
||||
glib::ParamSpecOverride::for_interface::<gtk::Scrollable>("vadjustment"),
|
||||
|
|
@ -276,6 +273,7 @@ mod imp {
|
|||
drag_controller.connect_drag_begin(|drag_controller, x, y| {
|
||||
let widget = drag_controller
|
||||
.widget()
|
||||
.unwrap()
|
||||
.dynamic_cast::<super::GraphView>()
|
||||
.expect("drag-begin event is not on the GraphView");
|
||||
let mut dragged_node = widget.imp().dragged_node.borrow_mut();
|
||||
|
|
@ -315,6 +313,7 @@ mod imp {
|
|||
drag_controller.connect_drag_update(|drag_controller, x, y| {
|
||||
let widget = drag_controller
|
||||
.widget()
|
||||
.unwrap()
|
||||
.dynamic_cast::<super::GraphView>()
|
||||
.expect("drag-update event is not on the GraphView");
|
||||
let dragged_node = widget.imp().dragged_node.borrow();
|
||||
|
|
@ -348,6 +347,7 @@ mod imp {
|
|||
controller.connect_enter(|controller, x, y| {
|
||||
let graph = controller
|
||||
.widget()
|
||||
.unwrap()
|
||||
.downcast::<super::GraphView>()
|
||||
.expect("Widget should be a graphview");
|
||||
|
||||
|
|
@ -357,6 +357,7 @@ mod imp {
|
|||
controller.connect_motion(|controller, x, y| {
|
||||
let graph = controller
|
||||
.widget()
|
||||
.unwrap()
|
||||
.downcast::<super::GraphView>()
|
||||
.expect("Widget should be a graphview");
|
||||
|
||||
|
|
@ -366,6 +367,7 @@ mod imp {
|
|||
controller.connect_leave(|controller| {
|
||||
let graph = controller
|
||||
.widget()
|
||||
.unwrap()
|
||||
.downcast::<super::GraphView>()
|
||||
.expect("Widget should be a graphview");
|
||||
|
||||
|
|
@ -386,7 +388,7 @@ mod imp {
|
|||
Port::static_type(),
|
||||
glib::Priority::DEFAULT,
|
||||
Option::<&gio::Cancellable>::None,
|
||||
clone!(@weak self as imp => move|value| {
|
||||
clone!(#[weak(rename_to = imp)] self, move|value| {
|
||||
let Ok(value) = value else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -430,6 +432,7 @@ mod imp {
|
|||
{
|
||||
let widget = eventcontroller
|
||||
.widget()
|
||||
.unwrap()
|
||||
.downcast::<super::GraphView>()
|
||||
.unwrap();
|
||||
widget.set_zoom_factor(widget.zoom_factor() + (0.1 * -delta_y), None);
|
||||
|
|
@ -445,7 +448,7 @@ mod imp {
|
|||
fn setup_zoom_gesture(&self) {
|
||||
let zoom_gesture = gtk::GestureZoom::new();
|
||||
zoom_gesture.connect_begin(|gesture, _| {
|
||||
let widget = gesture.widget().downcast::<super::GraphView>().unwrap();
|
||||
let widget = gesture.widget().unwrap().downcast::<super::GraphView>().unwrap();
|
||||
|
||||
widget
|
||||
.imp()
|
||||
|
|
@ -457,7 +460,7 @@ mod imp {
|
|||
.set(gesture.bounding_box_center());
|
||||
});
|
||||
zoom_gesture.connect_scale_changed(move |gesture, delta| {
|
||||
let widget = gesture.widget().downcast::<super::GraphView>().unwrap();
|
||||
let widget = gesture.widget().unwrap().downcast::<super::GraphView>().unwrap();
|
||||
|
||||
let initial_zoom = widget
|
||||
.imp()
|
||||
|
|
@ -480,6 +483,7 @@ mod imp {
|
|||
drag_controller.connect_drag_begin(|drag_controller, _, _| {
|
||||
let widget = drag_controller
|
||||
.widget()
|
||||
.unwrap()
|
||||
.downcast::<super::GraphView>()
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -489,6 +493,7 @@ mod imp {
|
|||
drag_controller.connect_drag_update(|drag_controller, x, y| {
|
||||
let widget = drag_controller
|
||||
.widget()
|
||||
.unwrap()
|
||||
.downcast::<super::GraphView>()
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -689,7 +694,7 @@ mod imp {
|
|||
|
||||
if let Some(adjustment) = adjustment {
|
||||
adjustment
|
||||
.connect_value_changed(clone!(@weak obj => move |_| obj.queue_allocate() ));
|
||||
.connect_value_changed(clone!(#[weak] obj, move |_| obj.queue_allocate() ));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -720,7 +725,8 @@ mod imp {
|
|||
|
||||
glib::wrapper! {
|
||||
pub struct GraphView(ObjectSubclass<imp::GraphView>)
|
||||
@extends gtk::Widget;
|
||||
@extends gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::ConstraintTarget, gtk::Buildable, gtk::Scrollable;
|
||||
}
|
||||
|
||||
impl GraphView {
|
||||
|
|
@ -822,13 +828,13 @@ impl GraphView {
|
|||
pub fn add_link(&self, link: Link) {
|
||||
link.connect_notify_local(
|
||||
Some("active"),
|
||||
glib::clone!(@weak self as graph => move |_, _| {
|
||||
glib::clone!(#[weak(rename_to = graph)] self, move |_, _| {
|
||||
graph.queue_draw();
|
||||
}),
|
||||
);
|
||||
link.connect_notify_local(
|
||||
Some("media-type"),
|
||||
glib::clone!(@weak self as graph => move |_, _| {
|
||||
glib::clone!(#[weak(rename_to = graph)] self, move |_, _| {
|
||||
graph.queue_draw();
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
|
||||
// Copyright 2026 Jaŭhien Lavonćjeŭ <jauhien.lavoncjeu@gmail.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 3 as published by
|
||||
|
|
@ -14,18 +15,16 @@
|
|||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{cell::Cell, sync::LazyLock};
|
||||
use pipewire::spa::param::format::MediaType;
|
||||
|
||||
use adw::{glib, prelude::*, subclass::prelude::*};
|
||||
use pipewire::spa::format::MediaType;
|
||||
|
||||
use super::Port;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub struct Link {
|
||||
pub output_port: glib::WeakRef<Port>,
|
||||
pub input_port: glib::WeakRef<Port>,
|
||||
|
|
@ -53,7 +52,7 @@ mod imp {
|
|||
|
||||
impl ObjectImpl for Link {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
static PROPERTIES: LazyLock<Vec<glib::ParamSpec>> = LazyLock::new(|| {
|
||||
vec![
|
||||
glib::ParamSpecObject::builder::<Port>("output-port")
|
||||
.flags(glib::ParamFlags::READWRITE)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
|
||||
// Copyright 2026 Jaŭhien Lavonćjeŭ <jauhien.lavoncjeu@gmail.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 3 as published by
|
||||
|
|
@ -14,19 +15,16 @@
|
|||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{cell::{Cell, RefCell}, collections::HashSet};
|
||||
use pipewire::spa::utils::Direction;
|
||||
|
||||
use adw::{glib, gtk, prelude::*, subclass::prelude::*};
|
||||
use pipewire::spa::Direction;
|
||||
|
||||
use super::Port;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
collections::HashSet,
|
||||
};
|
||||
|
||||
#[derive(glib::Properties, gtk::CompositeTemplate, Default)]
|
||||
#[properties(wrapper_type = super::Node)]
|
||||
#[template(file = "node.ui")]
|
||||
|
|
@ -149,7 +147,8 @@ mod imp {
|
|||
|
||||
glib::wrapper! {
|
||||
pub struct Node(ObjectSubclass<imp::Node>)
|
||||
@extends gtk::Widget;
|
||||
@extends gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::ConstraintTarget, gtk::Buildable;
|
||||
}
|
||||
|
||||
impl Node {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
|
||||
// Copyright 2026 Jaŭhien Lavonćjeŭ <jauhien.lavoncjeu@gmail.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 3 as published by
|
||||
|
|
@ -14,6 +15,9 @@
|
|||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{cell::{Cell, OnceCell}, sync::LazyLock};
|
||||
use pipewire::spa::{utils::Direction, param::format::MediaType};
|
||||
|
||||
use adw::{
|
||||
gdk,
|
||||
glib::{self, subclass::Signal},
|
||||
|
|
@ -21,18 +25,12 @@ use adw::{
|
|||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use pipewire::spa::Direction;
|
||||
|
||||
use super::PortHandle;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use once_cell::{sync::Lazy, unsync::OnceCell};
|
||||
use pipewire::spa::{format::MediaType, Direction};
|
||||
|
||||
/// Graphical representation of a pipewire port.
|
||||
#[derive(gtk::CompositeTemplate, glib::Properties)]
|
||||
#[properties(wrapper_type = super::Port)]
|
||||
|
|
@ -112,7 +110,7 @@ mod imp {
|
|||
}
|
||||
|
||||
fn signals() -> &'static [Signal] {
|
||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||
static SIGNALS: LazyLock<Vec<Signal>> = LazyLock::new(|| {
|
||||
vec![Signal::builder("port-toggled")
|
||||
// Provide id of output port and input port to signal handler.
|
||||
.param_types([<u32>::static_type(), <u32>::static_type()])
|
||||
|
|
@ -218,6 +216,7 @@ mod imp {
|
|||
drag_src.connect_drag_begin(|drag_source, _| {
|
||||
let port = drag_source
|
||||
.widget()
|
||||
.unwrap()
|
||||
.dynamic_cast::<super::Port>()
|
||||
.expect("Widget should be a Port");
|
||||
|
||||
|
|
@ -226,6 +225,7 @@ mod imp {
|
|||
drag_src.connect_drag_cancel(|drag_source, _, _| {
|
||||
let port = drag_source
|
||||
.widget()
|
||||
.unwrap()
|
||||
.dynamic_cast::<super::Port>()
|
||||
.expect("Widget should be a Port");
|
||||
|
||||
|
|
@ -241,6 +241,7 @@ mod imp {
|
|||
drop_target.connect_value_notify(|drop_target| {
|
||||
let port = drop_target
|
||||
.widget()
|
||||
.unwrap()
|
||||
.dynamic_cast::<super::Port>()
|
||||
.expect("Widget should be a Port");
|
||||
|
||||
|
|
@ -260,6 +261,7 @@ mod imp {
|
|||
drop_target.connect_drop(|drop_target, val, _, _| {
|
||||
let port = drop_target
|
||||
.widget()
|
||||
.unwrap()
|
||||
.dynamic_cast::<super::Port>()
|
||||
.expect("Widget should be a Port");
|
||||
let other_port = val
|
||||
|
|
@ -328,7 +330,8 @@ mod imp {
|
|||
|
||||
glib::wrapper! {
|
||||
pub struct Port(ObjectSubclass<imp::Port>)
|
||||
@extends gtk::Widget;
|
||||
@extends gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::ConstraintTarget, gtk::Buildable;
|
||||
}
|
||||
|
||||
impl Port {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
|
||||
// Copyright 2026 Jaŭhien Lavonćjeŭ <jauhien.lavoncjeu@gmail.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 3 as published by
|
||||
|
|
@ -61,7 +62,8 @@ mod imp {
|
|||
|
||||
glib::wrapper! {
|
||||
pub struct PortHandle(ObjectSubclass<imp::PortHandle>)
|
||||
@extends gtk::Widget;
|
||||
@extends gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::ConstraintTarget, gtk::Buildable;
|
||||
}
|
||||
|
||||
impl PortHandle {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
use adw::{glib, gtk, prelude::*, subclass::prelude::*};
|
||||
use std::{cell::RefCell, sync::LazyLock};
|
||||
|
||||
use adw::{glib::{self, clone}, gtk, prelude::*, subclass::prelude::*, gio};
|
||||
|
||||
use super::GraphView;
|
||||
|
||||
mod imp {
|
||||
use std::cell::RefCell;
|
||||
|
||||
use super::*;
|
||||
|
||||
use gtk::{gio, glib::clone};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
#[derive(gtk::CompositeTemplate)]
|
||||
#[template(file = "zoomentry.ui")]
|
||||
pub struct ZoomEntry {
|
||||
|
|
@ -66,7 +63,7 @@ mod imp {
|
|||
self.parent_constructed();
|
||||
|
||||
self.zoom_out_button
|
||||
.connect_clicked(clone!(@weak self as imp => move |_| {
|
||||
.connect_clicked(clone!(#[weak(rename_to = imp)] self, move |_| {
|
||||
let graphview = imp.graphview.borrow();
|
||||
if let Some(ref graphview) = *graphview {
|
||||
graphview.set_zoom_factor(graphview.zoom_factor() - 0.1, None);
|
||||
|
|
@ -74,7 +71,7 @@ mod imp {
|
|||
}));
|
||||
|
||||
self.zoom_in_button
|
||||
.connect_clicked(clone!(@weak self as imp => move |_| {
|
||||
.connect_clicked(clone!(#[weak(rename_to = imp)] self, move |_| {
|
||||
let graphview = imp.graphview.borrow();
|
||||
if let Some(ref graphview) = *graphview {
|
||||
graphview.set_zoom_factor(graphview.zoom_factor() + 0.1, None);
|
||||
|
|
@ -82,7 +79,7 @@ mod imp {
|
|||
}));
|
||||
|
||||
self.entry
|
||||
.connect_activate(clone!(@weak self as imp => move |entry| {
|
||||
.connect_activate(clone!(#[weak(rename_to = imp)] self, move |entry| {
|
||||
if let Ok(zoom_factor) = entry.text().trim_matches('%').parse::<f64>() {
|
||||
let graphview = imp.graphview.borrow();
|
||||
if let Some(ref graphview) = *graphview {
|
||||
|
|
@ -91,7 +88,7 @@ mod imp {
|
|||
}
|
||||
}));
|
||||
self.entry
|
||||
.connect_icon_press(clone!(@weak self as imp => move |_, pos| {
|
||||
.connect_icon_press(clone!(#[weak(rename_to = imp)] self, move |_, pos| {
|
||||
if pos == gtk::EntryIconPosition::Secondary {
|
||||
imp.popover.show();
|
||||
}
|
||||
|
|
@ -109,7 +106,7 @@ mod imp {
|
|||
}
|
||||
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
static PROPERTIES: LazyLock<Vec<glib::ParamSpec>> = LazyLock::new(|| {
|
||||
vec![glib::ParamSpecObject::builder::<GraphView>("zoomed-widget")
|
||||
.flags(glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT)
|
||||
.build()]
|
||||
|
|
@ -132,7 +129,7 @@ mod imp {
|
|||
if let Some(ref widget) = widget {
|
||||
widget.connect_notify_local(
|
||||
Some("zoom-factor"),
|
||||
clone!(@weak self as imp => move |graphview, _| {
|
||||
clone!(#[weak(rename_to = imp)] self, move |graphview, _| {
|
||||
imp.update_zoom_factor_text(graphview.zoom_factor());
|
||||
}),
|
||||
);
|
||||
|
|
@ -161,7 +158,8 @@ mod imp {
|
|||
|
||||
glib::wrapper! {
|
||||
pub struct ZoomEntry(ObjectSubclass<imp::ZoomEntry>)
|
||||
@extends gtk::Box, gtk::Widget;
|
||||
@extends gtk::Box, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::ConstraintTarget, gtk::Buildable;
|
||||
}
|
||||
|
||||
impl ZoomEntry {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,8 @@ mod imp {
|
|||
glib::wrapper! {
|
||||
pub struct Window(ObjectSubclass<imp::Window>)
|
||||
@extends adw::ApplicationWindow, gtk::ApplicationWindow, gtk::Window, gtk::Widget,
|
||||
@implements gio::ActionGroup, gio::ActionMap;
|
||||
@implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable,
|
||||
gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager;
|
||||
}
|
||||
|
||||
impl Window {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue