mirror of
https://github.com/ilya-zlobintsev/LACT.git
synced 2026-08-19 01:14:43 -05:00
feat: WIP service setup GUI
This commit is contained in:
Generated
+2
@@ -1743,6 +1743,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"cairo-rs",
|
||||
"divan",
|
||||
"futures",
|
||||
"gtk4",
|
||||
"i18n-embed",
|
||||
"i18n-embed-fl",
|
||||
@@ -1765,6 +1766,7 @@ dependencies = [
|
||||
"thread-priority",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -39,6 +39,7 @@ i18n-embed = { version = "0.16.0", features = [
|
||||
] }
|
||||
i18n-embed-fl = "0.10.0"
|
||||
rust-embed = { version = "8.11.0", features = ["debug-embed"] }
|
||||
zbus = { version = "5.14.0", default-features = false, features = ["tokio"] }
|
||||
|
||||
[profile.release]
|
||||
strip = "symbols"
|
||||
|
||||
@@ -19,7 +19,7 @@ use schema::{
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::{
|
||||
future::Future, os::unix::net::UnixStream, path::PathBuf, pin::Pin, rc::Rc, time::Duration,
|
||||
fmt, future::Future, os::unix::net::UnixStream, path::PathBuf, pin::Pin, rc::Rc, time::Duration,
|
||||
};
|
||||
use tokio::{
|
||||
net::ToSocketAddrs,
|
||||
@@ -222,6 +222,14 @@ impl DaemonClient {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for DaemonClient {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("DaemonClient")
|
||||
.field("embedded", &self.embedded)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_socket_path() -> Option<PathBuf> {
|
||||
let root_path = PathBuf::from("/run/lactd.sock");
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ serde_norway = { workspace = true }
|
||||
nvml-wrapper = "0.12.1"
|
||||
bitflags = "2.11.1"
|
||||
pciid-parser = { version = "0.8", features = ["serde"] }
|
||||
zbus = { version = "5.14.0", default-features = false, features = ["tokio"] }
|
||||
zbus = { workspace = true }
|
||||
libdrm_amdgpu_sys = { version = "0.8.13", default-features = false, features = [
|
||||
"dynamic_loading",
|
||||
] }
|
||||
|
||||
@@ -36,6 +36,9 @@ i18n-embed = { workspace = true }
|
||||
i18n-embed-fl = { workspace = true }
|
||||
rust-embed = { workspace = true }
|
||||
|
||||
futures = { workspace = true }
|
||||
zbus = { workspace = true }
|
||||
|
||||
plotters = { version = "0.3.7", default-features = false, features = [
|
||||
"line_series",
|
||||
"full_palette",
|
||||
|
||||
+83
-45
@@ -35,6 +35,10 @@ use crate::{
|
||||
},
|
||||
},
|
||||
config::WindowSize,
|
||||
service_setup::{
|
||||
ServiceSetupDialog, ServiceSetupDialogParams,
|
||||
systemd::{self, connect_unit_proxy},
|
||||
},
|
||||
};
|
||||
use adw::prelude::*;
|
||||
use anyhow::{Context, anyhow};
|
||||
@@ -69,8 +73,8 @@ use relm4::{
|
||||
css,
|
||||
loading_widgets::LoadingWidgets,
|
||||
new_action_group, new_stateless_action,
|
||||
prelude::{AsyncComponent, AsyncComponentParts},
|
||||
tokio::{self, time::sleep},
|
||||
prelude::{AsyncComponent, AsyncComponentController, AsyncComponentParts},
|
||||
tokio::{self, sync::oneshot, time::sleep},
|
||||
view,
|
||||
};
|
||||
use relm4_components::{
|
||||
@@ -156,6 +160,7 @@ impl AsyncComponent for AppModel {
|
||||
&fl!(I18N, "dump-vbios") => DumpVBiosAction,
|
||||
},
|
||||
section! {
|
||||
"Service Setup" => ServiceSetupAction,
|
||||
&fl!(I18N, "preferences") => PreferencesAction,
|
||||
&fl!(I18N, "about") => AboutAction,
|
||||
},
|
||||
@@ -364,6 +369,10 @@ impl AsyncComponent for AppModel {
|
||||
// 5. build child components,
|
||||
// 6. load profiles and initial GPU data.
|
||||
|
||||
let application = root
|
||||
.application()
|
||||
.expect("Failed to get application from root window");
|
||||
|
||||
relm4::set_global_css_with_priority(
|
||||
styles::COMBINED_CSS,
|
||||
STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||
@@ -373,7 +382,40 @@ impl AsyncComponent for AppModel {
|
||||
error!("could not apply theme: {err:#}");
|
||||
}
|
||||
|
||||
let (daemon_client, conn_err) = match args.tcp_address {
|
||||
let daemon_client = match DaemonClient::connect().await {
|
||||
Ok(client) => client,
|
||||
Err(err) => {
|
||||
let configured_client = match connect_unit_proxy().await {
|
||||
Ok(unit_proxy) => {
|
||||
let params = ServiceSetupDialogParams {
|
||||
parent: root.clone().upcast(),
|
||||
initial_error: err,
|
||||
unit_proxy,
|
||||
};
|
||||
let service_setup =
|
||||
ServiceSetupDialog::builder().launch(params).into_stream();
|
||||
|
||||
service_setup
|
||||
.recv_one()
|
||||
.await
|
||||
.expect("Could not get client")
|
||||
}
|
||||
Err(_err) => {
|
||||
// TODO: show error about no systemd
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
match configured_client {
|
||||
Some(client) => client,
|
||||
None => create_embedded_connection()
|
||||
.await
|
||||
.expect("Could not spawn embedded daemon"),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*let (daemon_client, conn_err) = match args.tcp_address {
|
||||
Some(remote_addr) => {
|
||||
info!("establishing connection to {remote_addr}");
|
||||
match DaemonClient::connect_tcp(&remote_addr).await {
|
||||
@@ -390,7 +432,7 @@ impl AsyncComponent for AppModel {
|
||||
None => create_connection()
|
||||
.await
|
||||
.expect("Could not establish any daemon connection"),
|
||||
};
|
||||
};*/
|
||||
|
||||
let mut conn_status_rx = daemon_client.status_receiver();
|
||||
relm4::spawn_local(clone!(
|
||||
@@ -468,13 +510,11 @@ impl AsyncComponent for AppModel {
|
||||
// create action group and actions for app menu
|
||||
// action group and actions are declared at the bottom of the file
|
||||
let mut actions = RelmActionGroup::<AppActionGroup>::new();
|
||||
let application = root
|
||||
.application()
|
||||
.expect("Failed to get application from root window");
|
||||
setup_actions! {
|
||||
(actions, ProcessMonitorAction, APP_BROKER.send(AppMsg::ShowProcessMonitor)),
|
||||
(actions, GenerateDebugSnapshotAction, APP_BROKER.send(AppMsg::DebugSnapshot)),
|
||||
(actions, PreferencesAction, APP_BROKER.send(AppMsg::ShowPreferencesDialog)),
|
||||
(actions, ServiceSetupAction, APP_BROKER.send(AppMsg::ShowServiceSetupDialog)),
|
||||
(actions, AboutAction, APP_BROKER.send(AppMsg::ShowAboutDialog)),
|
||||
(actions, QuitAction, APP_BROKER.send(AppMsg::Quit)),
|
||||
}
|
||||
@@ -550,21 +590,21 @@ impl AsyncComponent for AppModel {
|
||||
.set_title(&page.title().unwrap_or_default());
|
||||
}
|
||||
|
||||
if let Some(err) = conn_err {
|
||||
model
|
||||
.info_dialog
|
||||
.emit(InfoDialogMsg::Show(Box::new(InfoDialogData {
|
||||
id: InfoDialogId::EmbeddedDaemonInfo,
|
||||
heading: fl!(I18N, "daemon-info-heading"),
|
||||
body: fl!(
|
||||
I18N,
|
||||
"embedded-daemon-info",
|
||||
error_info = format!("Error info: {err:#}\n\n")
|
||||
),
|
||||
selectable_text: Some("sudo systemctl enable --now lactd".to_string()),
|
||||
..Default::default()
|
||||
})));
|
||||
}
|
||||
// if let Some(err) = conn_err {
|
||||
// model
|
||||
// .info_dialog
|
||||
// .emit(InfoDialogMsg::Show(Box::new(InfoDialogData {
|
||||
// id: InfoDialogId::EmbeddedDaemonInfo,
|
||||
// heading: fl!(I19N, "daemon-info-heading"),
|
||||
// body: fl!(
|
||||
// I19N,
|
||||
// "embedded-daemon-info",
|
||||
// error_info = format!("Error info: {err:#}\n\n")
|
||||
// ),
|
||||
// selectable_text: Some("sudo systemctl enable --now lactd".to_string()),
|
||||
// ..Default::default()
|
||||
// })));
|
||||
// }
|
||||
|
||||
if let Some(info) = version_mismatch_info {
|
||||
model.info_dialog.emit(InfoDialogMsg::Show(Box::new(info)));
|
||||
@@ -673,6 +713,15 @@ impl AppModel {
|
||||
AppMsg::ShowOverdriveDialog => {
|
||||
self.overdrive_dialog.emit(OverdriveDialogMsg::Show);
|
||||
}
|
||||
AppMsg::ShowServiceSetupDialog => {
|
||||
let params = ServiceSetupDialogParams {
|
||||
parent: root.clone().upcast(),
|
||||
initial_error: anyhow!("TODO"),
|
||||
unit_proxy: systemd::connect_unit_proxy().await?,
|
||||
};
|
||||
let mut controller = ServiceSetupDialog::builder().launch(params).detach();
|
||||
controller.detach_runtime();
|
||||
}
|
||||
AppMsg::SelectProfile {
|
||||
profile,
|
||||
auto_switch,
|
||||
@@ -877,7 +926,7 @@ impl AppModel {
|
||||
}
|
||||
AppMsg::ConnectionStatus(status) => match status {
|
||||
ConnectionStatusMsg::Disconnected => {
|
||||
widgets.reconnecting_dialog.present(Some(root))
|
||||
// widgets.reconnecting_dialog.present(Some(root))
|
||||
}
|
||||
ConnectionStatusMsg::Reconnected => widgets.reconnecting_dialog.force_close(),
|
||||
},
|
||||
@@ -1396,36 +1445,25 @@ fn start_stats_update_loop(
|
||||
})
|
||||
}
|
||||
|
||||
async fn create_connection() -> anyhow::Result<(DaemonClient, Option<anyhow::Error>)> {
|
||||
match DaemonClient::connect().await {
|
||||
Ok(connection) => {
|
||||
debug!("Established daemon connection");
|
||||
Ok((connection, None))
|
||||
async fn create_embedded_connection() -> anyhow::Result<DaemonClient> {
|
||||
let (server_stream, client_stream) = UnixStream::pair()?;
|
||||
client_stream.set_nonblocking(true)?;
|
||||
server_stream.set_nonblocking(true)?;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if let Err(err) = lact_daemon::run_embedded(server_stream) {
|
||||
error!("Builtin daemon error: {err}");
|
||||
}
|
||||
Err(err) => {
|
||||
info!("could not connect to socket: {err:#}");
|
||||
info!("using a local daemon");
|
||||
});
|
||||
|
||||
let (server_stream, client_stream) = UnixStream::pair()?;
|
||||
client_stream.set_nonblocking(true)?;
|
||||
server_stream.set_nonblocking(true)?;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if let Err(err) = lact_daemon::run_embedded(server_stream) {
|
||||
error!("Builtin daemon error: {err}");
|
||||
}
|
||||
});
|
||||
|
||||
let client = DaemonClient::from_stream(client_stream, true)?;
|
||||
Ok((client, Some(err)))
|
||||
}
|
||||
}
|
||||
Ok(DaemonClient::from_stream(client_stream, true)?)
|
||||
}
|
||||
|
||||
new_action_group!(pub AppActionGroup, "app");
|
||||
new_stateless_action!(pub ProcessMonitorAction, AppActionGroup, "show-process-monitor");
|
||||
new_stateless_action!(pub GenerateDebugSnapshotAction, AppActionGroup, "generate-debug-snapshot");
|
||||
new_stateless_action!(pub DumpVBiosAction, AppActionGroup, "dump-vbios");
|
||||
new_stateless_action!(pub ServiceSetupAction, AppActionGroup, "service-setup");
|
||||
new_stateless_action!(pub PreferencesAction, AppActionGroup, "preferences");
|
||||
new_stateless_action!(pub AboutAction, AppActionGroup, "about");
|
||||
new_stateless_action!(pub QuitAction, AppActionGroup, "quit");
|
||||
|
||||
@@ -28,6 +28,7 @@ pub enum AppMsg {
|
||||
ShowPreferencesDialog,
|
||||
ShowAboutDialog,
|
||||
ShowOverdriveDialog,
|
||||
ShowServiceSetupDialog,
|
||||
EnableOverdrive,
|
||||
DisableOverdrive,
|
||||
ResetConfig,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod app;
|
||||
mod config;
|
||||
mod service_setup;
|
||||
|
||||
use std::{
|
||||
panic,
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
pub mod systemd;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::service_setup::systemd::{START_MODE_REPLACE, UnitProxy};
|
||||
use adw::prelude::*;
|
||||
use anyhow::{Context as _, anyhow};
|
||||
use futures::StreamExt as _;
|
||||
use lact_client::DaemonClient;
|
||||
use relm4::{
|
||||
AsyncComponentSender, RelmWidgetExt,
|
||||
prelude::{AsyncComponent, AsyncComponentParts},
|
||||
tokio,
|
||||
};
|
||||
use tracing::{debug, warn};
|
||||
|
||||
pub struct ServiceSetupDialog {
|
||||
current_client: anyhow::Result<DaemonClient>,
|
||||
unit_proxy: UnitProxy<'static>,
|
||||
|
||||
service_state: String,
|
||||
}
|
||||
|
||||
pub struct ServiceSetupDialogParams {
|
||||
pub parent: gtk::ApplicationWindow,
|
||||
pub initial_error: anyhow::Error,
|
||||
pub unit_proxy: UnitProxy<'static>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ServiceSetupDialogMsg {
|
||||
Reconnect,
|
||||
StartService,
|
||||
RestartService,
|
||||
StopService,
|
||||
ServiceState(String),
|
||||
Close,
|
||||
// Show,
|
||||
}
|
||||
|
||||
#[relm4::component(pub, async)]
|
||||
impl AsyncComponent for ServiceSetupDialog {
|
||||
type Init = ServiceSetupDialogParams;
|
||||
type Input = ServiceSetupDialogMsg;
|
||||
type Output = Option<DaemonClient>;
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
adw::Dialog {
|
||||
set_content_width: 500,
|
||||
set_follows_content_size: true,
|
||||
set_title: "Service Setup",
|
||||
|
||||
connect_closed => ServiceSetupDialogMsg::Close,
|
||||
|
||||
#[wrap(Some)]
|
||||
set_child = &adw::ToolbarView {
|
||||
add_top_bar = &adw::HeaderBar {},
|
||||
|
||||
#[wrap(Some)]
|
||||
set_content = >k::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 10,
|
||||
set_margin_all: 10,
|
||||
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_markup: &format!("Service Status: <tt>{}</tt>", model.service_state),
|
||||
},
|
||||
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_markup: &format!("Connection ok: <tt>{}</tt>", model.current_client.is_ok()),
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: "Start Service",
|
||||
connect_clicked => ServiceSetupDialogMsg::StartService,
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: "Stop Service",
|
||||
connect_clicked => ServiceSetupDialogMsg::StopService,
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: "Restart Service",
|
||||
connect_clicked => ServiceSetupDialogMsg::RestartService,
|
||||
},
|
||||
},
|
||||
|
||||
add_bottom_bar = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
set_halign: gtk::Align::Fill,
|
||||
set_margin_horizontal: 10,
|
||||
set_margin_bottom: 10,
|
||||
|
||||
gtk::Button {
|
||||
set_halign: gtk::Align::End,
|
||||
set_hexpand: true,
|
||||
set_label: "Close",
|
||||
|
||||
connect_clicked[root] => move |_| {
|
||||
root.close();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn init(
|
||||
params: Self::Init,
|
||||
root: Self::Root,
|
||||
sender: AsyncComponentSender<Self>,
|
||||
) -> AsyncComponentParts<Self> {
|
||||
let mut state_stream = params.unit_proxy.receive_active_state_changed().await;
|
||||
|
||||
let input_sender = sender.input_sender().clone();
|
||||
relm4::spawn(async move {
|
||||
while let Some(property) = state_stream.next().await {
|
||||
match property.get().await {
|
||||
Ok(state) => {
|
||||
if input_sender
|
||||
.send(ServiceSetupDialogMsg::ServiceState(state))
|
||||
.is_err()
|
||||
{
|
||||
debug!("service setup dialog closed, exiting service state watcher");
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("could not get service state: {err:#}");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let input_sender = sender.input_sender().clone();
|
||||
relm4::spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_millis(250)).await;
|
||||
if input_sender.send(ServiceSetupDialogMsg::Reconnect).is_err() {
|
||||
debug!("service setup dialog closed, exiting client watcher");
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let service_state = params
|
||||
.unit_proxy
|
||||
.active_state()
|
||||
.await
|
||||
.unwrap_or_else(|err| {
|
||||
// TODO: show error, APP_BROKER does not work yet because app is not initialized
|
||||
// APP_BROKER.send(AppMsg::Error(Arc::new(anyhow!("systemd error: {err:#}"))));
|
||||
panic!("{err:#}");
|
||||
});
|
||||
|
||||
let model = Self {
|
||||
current_client: Err(params.initial_error),
|
||||
unit_proxy: params.unit_proxy,
|
||||
service_state,
|
||||
};
|
||||
let widgets = view_output!();
|
||||
|
||||
root.present(Some(¶ms.parent));
|
||||
|
||||
AsyncComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
async fn update(
|
||||
&mut self,
|
||||
msg: Self::Input,
|
||||
sender: AsyncComponentSender<Self>,
|
||||
_root: &Self::Root,
|
||||
) {
|
||||
if let Err(err) = self.handle_msg(msg, sender).await {
|
||||
// TODO
|
||||
panic!("{err:#}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ServiceSetupDialog {
|
||||
async fn handle_msg(
|
||||
&mut self,
|
||||
msg: ServiceSetupDialogMsg,
|
||||
sender: AsyncComponentSender<Self>,
|
||||
) -> anyhow::Result<()> {
|
||||
match msg {
|
||||
ServiceSetupDialogMsg::Reconnect => (),
|
||||
ServiceSetupDialogMsg::StartService => {
|
||||
self.unit_proxy.start(START_MODE_REPLACE).await?;
|
||||
}
|
||||
ServiceSetupDialogMsg::RestartService => {
|
||||
self.unit_proxy.restart(START_MODE_REPLACE).await?;
|
||||
}
|
||||
ServiceSetupDialogMsg::StopService => {
|
||||
self.unit_proxy.stop(START_MODE_REPLACE).await?;
|
||||
}
|
||||
ServiceSetupDialogMsg::ServiceState(state) => {
|
||||
self.service_state = state;
|
||||
}
|
||||
ServiceSetupDialogMsg::Close => {
|
||||
let client = self.current_client.as_ref().ok().cloned();
|
||||
sender.output(client).unwrap();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
self.reconnect().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn reconnect(&mut self) -> anyhow::Result<()> {
|
||||
self.current_client = DaemonClient::connect().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
//! # D-Bus interface proxy for: `org.freedesktop.systemd1.Manager`
|
||||
use anyhow::Context;
|
||||
use zbus::{proxy, zvariant::OwnedObjectPath};
|
||||
|
||||
const UNIT_NAME: &str = "lactd.service";
|
||||
|
||||
pub const UNIT_STATE_ACTIVE: &str = "active";
|
||||
pub const UNIT_STATE_INACTIVE: &str = "inactive";
|
||||
pub const UNIT_STATE_FAILED: &str = "failed";
|
||||
|
||||
pub const START_MODE_REPLACE: &str = "replace";
|
||||
|
||||
pub async fn connect_unit_proxy() -> anyhow::Result<UnitProxy<'static>> {
|
||||
let conn = zbus::Connection::system()
|
||||
.await
|
||||
.context("Could not establish DBus connection")?;
|
||||
|
||||
let manager = ManagerProxy::new(&conn)
|
||||
.await
|
||||
.context("Could not connect to systemd manager interface")?;
|
||||
|
||||
let path = manager
|
||||
.get_unit(UNIT_NAME)
|
||||
.await
|
||||
.context("Could not get lact systemd unit")?;
|
||||
|
||||
let unit = UnitProxy::builder(&conn)
|
||||
.path(path)?
|
||||
.build()
|
||||
.await
|
||||
.context("Could not connect to systemd unit interface")?;
|
||||
|
||||
Ok(unit)
|
||||
}
|
||||
|
||||
#[proxy(
|
||||
interface = "org.freedesktop.systemd1.Manager",
|
||||
default_service = "org.freedesktop.systemd1",
|
||||
default_path = "/org/freedesktop/systemd1"
|
||||
)]
|
||||
pub trait Manager {
|
||||
#[zbus(allow_interactive_auth)]
|
||||
fn get_unit(&self, name: &str) -> zbus::Result<OwnedObjectPath>;
|
||||
|
||||
#[zbus(allow_interactive_auth)]
|
||||
fn enable_unit_files(
|
||||
&self,
|
||||
files: &[&str],
|
||||
runtime: bool,
|
||||
force: bool,
|
||||
) -> zbus::Result<(bool, Vec<(String, String, String)>)>;
|
||||
}
|
||||
|
||||
#[proxy(
|
||||
interface = "org.freedesktop.systemd1.Unit",
|
||||
default_service = "org.freedesktop.systemd1"
|
||||
)]
|
||||
pub trait Unit {
|
||||
#[zbus(allow_interactive_auth)]
|
||||
fn restart(&self, mode: &str) -> zbus::Result<OwnedObjectPath>;
|
||||
|
||||
#[zbus(allow_interactive_auth)]
|
||||
fn start(&self, mode: &str) -> zbus::Result<OwnedObjectPath>;
|
||||
|
||||
#[zbus(allow_interactive_auth)]
|
||||
fn stop(&self, mode: &str) -> zbus::Result<OwnedObjectPath>;
|
||||
|
||||
#[zbus(property)]
|
||||
fn active_state(&self) -> zbus::Result<String>;
|
||||
}
|
||||
Reference in New Issue
Block a user