Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
4bc439feaf | |||
c9b07c92dc | |||
2fefc6ec65 |
@ -6,6 +6,7 @@ edition = "2021"
|
||||
[dependencies]
|
||||
gettext-rs = { version = "0.7", features = ["gettext-system"] }
|
||||
gtk = { version = "0.9", package = "gtk4", features = ["gnome_47"] }
|
||||
pipewire = "0.8.0"
|
||||
|
||||
[dependencies.adw]
|
||||
package = "libadwaita"
|
||||
|
@ -1,10 +1,11 @@
|
||||
{
|
||||
"id" : "de.AdaLouBaumann.AudioDeviceManager",
|
||||
"runtime" : "org.gnome.Platform",
|
||||
"runtime-version" : "master",
|
||||
"runtime-version" : "22.08",
|
||||
"sdk" : "org.gnome.Sdk",
|
||||
"sdk-extensions" : [
|
||||
"org.freedesktop.Sdk.Extension.rust-stable"
|
||||
"org.freedesktop.Sdk.Extension.rust-stable",
|
||||
"org.freedesktop.Sdk.Extension.llvm14"
|
||||
],
|
||||
"command" : "audio-device-manager",
|
||||
"finish-args" : [
|
||||
@ -21,7 +22,8 @@
|
||||
],
|
||||
"env" : {
|
||||
"RUST_BACKTRACE" : "1",
|
||||
"RUST_LOG" : "audio-device-manager=debug"
|
||||
"RUST_LOG" : "audio-device-manager=debug",
|
||||
"INCLUDE" : "/lib/clang/14.0.6/include"
|
||||
}
|
||||
},
|
||||
"cleanup" : [
|
||||
@ -44,7 +46,7 @@
|
||||
{
|
||||
"type" : "git",
|
||||
"url" : "https://gitea.ada-baumann.de/ada/AudioDeviceManager",
|
||||
"branch" : "main"
|
||||
"branch" : "dev"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
12
src/components.rs
Normal file
12
src/components.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use adw::gdk::pango;
|
||||
use gtk::ListBoxRow;
|
||||
|
||||
pub(crate) fn new_device(name: String) -> ListBoxRow {
|
||||
let device_label = gtk::Label::builder()
|
||||
.ellipsize(pango::EllipsizeMode::End)
|
||||
.xalign(0.0)
|
||||
.label(&name)
|
||||
.build();
|
||||
|
||||
ListBoxRow::builder().child(&device_label).build()
|
||||
}
|
@ -22,6 +22,8 @@
|
||||
mod application;
|
||||
mod config;
|
||||
mod window;
|
||||
mod components;
|
||||
mod pipewire;
|
||||
|
||||
use self::application::AudioDeviceManagerApplication;
|
||||
use self::window::AudioDeviceManagerWindow;
|
||||
|
57
src/pipewire.rs
Normal file
57
src/pipewire.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use std::thread;
|
||||
use std::thread::JoinHandle;
|
||||
use gtk::glib::ExitCode;
|
||||
use pipewire::{context::Context, main_loop::MainLoop};
|
||||
use pipewire::types::ObjectType;
|
||||
|
||||
pub fn spawn_pipewire_thread() -> JoinHandle<ExitCode> {
|
||||
let pw_thread = thread::spawn(|| {
|
||||
// Initialize PipeWire and run the main loop
|
||||
// ...
|
||||
let mainloop = MainLoop::new(None).expect("failed to get mail loop");
|
||||
let context = Context::new(&mainloop).expect("failed to get context");
|
||||
let core = context.connect(None).expect("failed to get core");
|
||||
let registry = core.get_registry().expect("failed to get registry");
|
||||
|
||||
let _listener = registry
|
||||
.add_listener_local()
|
||||
.global(|global|
|
||||
{
|
||||
if global.type_ == ObjectType::Port {
|
||||
let props = global.props.as_ref().unwrap();
|
||||
let port_name = props.get("port.name");
|
||||
let port_alias = props.get("port.alias");
|
||||
let object_path = props.get("object.path");
|
||||
let format_dsp = props.get("format.dsp");
|
||||
let audio_channel = props.get("audio.channel");
|
||||
let port_id = props.get("port.id");
|
||||
let port_direction = props.get("port.direction");
|
||||
println!("Port: Name: {:?} Alias: {:?} Id: {:?} Direction: {:?} AudioChannel: {:?} Object Path: {:?} FormatDsp: {:?}",
|
||||
port_name,
|
||||
port_alias,
|
||||
port_id,port_direction,audio_channel,object_path,format_dsp
|
||||
);
|
||||
} else if global.type_ == ObjectType::Device {
|
||||
let props = global.props.as_ref().unwrap();
|
||||
let device_name = props.get("device.name");
|
||||
let device_nick = props.get("device.nick");
|
||||
let device_description = props.get("device.description");
|
||||
let device_api = props.get("device.api");
|
||||
let media_class = props.get("media.class");
|
||||
println!("Device: Name: {:?} Nick: {:?} Desc: {:?} Api: {:?} MediaClass: {:?}",
|
||||
device_name, device_nick, device_description, device_api, media_class);
|
||||
}
|
||||
}
|
||||
)
|
||||
.register();
|
||||
|
||||
// Calling the `destroy_global` method on the registry will destroy the object with the specified id on the remote.
|
||||
// We don't have a specific object to destroy now, so this is commented out.
|
||||
// registry.destroy_global(313).into_result()?;
|
||||
|
||||
|
||||
mainloop.run();
|
||||
return ExitCode::FAILURE; // not sure
|
||||
});
|
||||
return pw_thread;
|
||||
}
|
@ -6,15 +6,24 @@ template $AudioDeviceManagerWindow: Adw.ApplicationWindow {
|
||||
default-width: 800;
|
||||
default-height: 600;
|
||||
|
||||
content: Adw.NavigationSplitView {
|
||||
Adw.Breakpoint {
|
||||
condition ("max-width: 500sp")
|
||||
setters {
|
||||
split_view.collapsed: true;
|
||||
}
|
||||
}
|
||||
|
||||
content: Adw.NavigationSplitView split_view {
|
||||
min-sidebar-width: 200;
|
||||
|
||||
sidebar: Adw.NavigationPage {
|
||||
title: _("Sidebar");
|
||||
tag: "sidebar";
|
||||
title: _("Devices");
|
||||
tag: "devices";
|
||||
|
||||
child: Adw.ToolbarView {
|
||||
[top]
|
||||
Adw.HeaderBar {
|
||||
show-title: false;
|
||||
show-title: true;
|
||||
[start]
|
||||
Gtk.ToggleButton {
|
||||
icon-name: "list-add-symbolic";
|
||||
@ -33,14 +42,14 @@ template $AudioDeviceManagerWindow: Adw.ApplicationWindow {
|
||||
};
|
||||
};
|
||||
|
||||
content: Adw.NavigationPage {
|
||||
title: _("Content");
|
||||
tag: "content";
|
||||
content: Adw.NavigationPage device_navigation_page {
|
||||
title: _("");
|
||||
tag: "device navigation page";
|
||||
|
||||
child: Adw.ToolbarView {
|
||||
[top]
|
||||
Adw.HeaderBar {
|
||||
show-title: false;
|
||||
show-title: true;
|
||||
[end]
|
||||
MenuButton {
|
||||
primary: true;
|
||||
@ -50,13 +59,15 @@ template $AudioDeviceManagerWindow: Adw.ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
content: Adw.StatusPage {
|
||||
title: _("Content");
|
||||
content: Gtk.ScrolledWindow {
|
||||
child: Adw.Clamp device_page_clamp {
|
||||
maximum-size: 640;
|
||||
|
||||
LinkButton {
|
||||
label: _("API Reference");
|
||||
uri: "https://ada-baumann.de";
|
||||
}
|
||||
Box {
|
||||
orientation: vertical;
|
||||
spacing: 24;
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
128
src/window.rs
128
src/window.rs
@ -18,10 +18,13 @@
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
use adw::glib::{clone, closure_local};
|
||||
use adw::prelude::{AlertDialogExt, AlertDialogExtManual, NavigationPageExt};
|
||||
use adw::ResponseAppearance;
|
||||
use gtk::prelude::*;
|
||||
use adw::subclass::prelude::*;
|
||||
use gtk::{gio, glib};
|
||||
use gtk::{gio, glib, Button, ListBoxRow};
|
||||
use crate::components::new_device;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
@ -32,6 +35,10 @@ mod imp {
|
||||
// Template widgets
|
||||
#[template_child]
|
||||
pub devices_list: TemplateChild<gtk::ListBox>,
|
||||
#[template_child]
|
||||
pub device_page_clamp: TemplateChild<adw::Clamp>,
|
||||
#[template_child]
|
||||
pub device_navigation_page: TemplateChild<adw::NavigationPage>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
@ -42,6 +49,15 @@ mod imp {
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.bind_template();
|
||||
|
||||
// Create async action to create new device and add to action group "win"
|
||||
klass.install_action_async(
|
||||
"win.new-device",
|
||||
None,
|
||||
|window, _, _| async move {
|
||||
window.new_device().await;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||
@ -58,13 +74,115 @@ mod imp {
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct AudioDeviceManagerWindow(ObjectSubclass<imp::AudioDeviceManagerWindow>)
|
||||
@extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow, @implements gio::ActionGroup, gio::ActionMap;
|
||||
@extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow,
|
||||
@implements gio::ActionGroup, gio::ActionMap;
|
||||
}
|
||||
|
||||
impl AudioDeviceManagerWindow {
|
||||
pub fn new<P: IsA<gtk::Application>>(application: &P) -> Self {
|
||||
glib::Object::builder()
|
||||
let instance: AudioDeviceManagerWindow = glib::Object::builder()
|
||||
.property("application", application)
|
||||
.build()
|
||||
.build();
|
||||
|
||||
instance.bind_signals();
|
||||
|
||||
instance
|
||||
}
|
||||
|
||||
fn bind_signals(&self) {
|
||||
self.imp().devices_list.connect_row_activated(clone!(
|
||||
#[weak(rename_to = window)]
|
||||
self,
|
||||
move |_, row| {
|
||||
println!("Row selected {}, {:?}", row.index(), row);
|
||||
let label: gtk::Label = row
|
||||
.child()
|
||||
.and_downcast()
|
||||
.expect("No Label in Row");
|
||||
window.select_device(label.text().to_string());
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
async fn new_device(&self) {
|
||||
let entry = gtk::Entry::builder()
|
||||
.placeholder_text("Name")
|
||||
.activates_default(true)
|
||||
.build();
|
||||
|
||||
let cancel_response = "cancel";
|
||||
let create_response = "create";
|
||||
|
||||
// Create new dialog
|
||||
let dialog = adw::AlertDialog::builder()
|
||||
.heading("New Device")
|
||||
.close_response(cancel_response)
|
||||
.default_response(create_response)
|
||||
.extra_child(&entry)
|
||||
.build();
|
||||
|
||||
dialog.add_responses(&[(cancel_response, "Cancel"), (create_response, "Create")]);
|
||||
|
||||
// Make the dialog button insensitive initially
|
||||
dialog.set_response_enabled(create_response, false);
|
||||
dialog.set_response_appearance(create_response, ResponseAppearance::Suggested);
|
||||
|
||||
// Set entry's css class to "error", when there is no text in it
|
||||
entry.connect_changed(clone!(
|
||||
#[weak]
|
||||
dialog,
|
||||
move |entry| {
|
||||
let text = entry.text();
|
||||
let empty = text.is_empty();
|
||||
|
||||
dialog.set_response_enabled(create_response, !empty);
|
||||
|
||||
if empty {
|
||||
entry.add_css_class("error");
|
||||
} else {
|
||||
entry.remove_css_class("error");
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
let response = dialog.choose_future(self).await;
|
||||
|
||||
// Return if the user chose 'cancel_response'
|
||||
|
||||
if response == cancel_response {
|
||||
println!("Cancel");
|
||||
return;
|
||||
}
|
||||
|
||||
let device = new_device(entry.text().to_string());
|
||||
|
||||
self.imp().devices_list.append(&device);
|
||||
}
|
||||
|
||||
fn select_device(&self, name: String) {
|
||||
self.imp().device_navigation_page.set_title(&name);
|
||||
|
||||
let device_page = self.build_device_page(name);
|
||||
|
||||
self.imp().device_page_clamp.set_child(Some(&device_page));
|
||||
}
|
||||
|
||||
fn build_device_page(&self, name: String) -> gtk::Box {
|
||||
let device_page = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.margin_start(12)
|
||||
.margin_end(12)
|
||||
.spacing(12)
|
||||
.build();
|
||||
|
||||
let entry = gtk::Entry::builder()
|
||||
.placeholder_text("Test")
|
||||
.secondary_icon_name("list-add-symbolic")
|
||||
.build();
|
||||
|
||||
device_page.append(&entry);
|
||||
|
||||
device_page
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user