Implement Gtk Network Status (#165)
Implemented - Switch reacts to burrow socket and network changes - meson as build system - Basic diagnostics to ensure burrow is installed properly - Flatpak / Meson Building
This commit is contained in:
parent
baa81eb939
commit
6990f90c2e
31 changed files with 1571 additions and 665 deletions
1
burrow-gtk/src/.gitignore
vendored
Normal file
1
burrow-gtk/src/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
config.rs
|
||||
136
burrow-gtk/src/components/app.rs
Normal file
136
burrow-gtk/src/components/app.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
use super::*;
|
||||
use anyhow::Context;
|
||||
use std::time::Duration;
|
||||
|
||||
const RECONNECT_POLL_TIME: Duration = Duration::from_secs(5);
|
||||
|
||||
pub struct App {
|
||||
daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
||||
_settings_screen: Controller<settings_screen::SettingsScreen>,
|
||||
switch_screen: AsyncController<switch_screen::SwitchScreen>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AppMsg {
|
||||
None,
|
||||
PostInit,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn run() {
|
||||
let app = RelmApp::new(config::ID);
|
||||
Self::setup_gresources().unwrap();
|
||||
Self::setup_i18n().unwrap();
|
||||
|
||||
app.run_async::<App>(());
|
||||
}
|
||||
|
||||
fn setup_i18n() -> Result<()> {
|
||||
gettextrs::setlocale(gettextrs::LocaleCategory::LcAll, "");
|
||||
gettextrs::bindtextdomain(config::GETTEXT_PACKAGE, config::LOCALEDIR)?;
|
||||
gettextrs::bind_textdomain_codeset(config::GETTEXT_PACKAGE, "UTF-8")?;
|
||||
gettextrs::textdomain(config::GETTEXT_PACKAGE)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_gresources() -> Result<()> {
|
||||
gtk::gio::resources_register_include!("compiled.gresource")
|
||||
.context("Failed to register and include compiled gresource.")
|
||||
}
|
||||
}
|
||||
|
||||
#[relm4::component(pub, async)]
|
||||
impl AsyncComponent for App {
|
||||
type Init = ();
|
||||
type Input = AppMsg;
|
||||
type Output = ();
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
adw::Window {
|
||||
set_title: Some("Burrow"),
|
||||
set_default_size: (640, 480),
|
||||
}
|
||||
}
|
||||
|
||||
async fn init(
|
||||
_: Self::Init,
|
||||
root: Self::Root,
|
||||
sender: AsyncComponentSender<Self>,
|
||||
) -> AsyncComponentParts<Self> {
|
||||
let daemon_client = Arc::new(Mutex::new(DaemonClient::new().await.ok()));
|
||||
|
||||
let switch_screen = switch_screen::SwitchScreen::builder()
|
||||
.launch(switch_screen::SwitchScreenInit {
|
||||
daemon_client: Arc::clone(&daemon_client),
|
||||
})
|
||||
.forward(sender.input_sender(), |_| AppMsg::None);
|
||||
|
||||
let settings_screen = settings_screen::SettingsScreen::builder()
|
||||
.launch(settings_screen::SettingsScreenInit {
|
||||
daemon_client: Arc::clone(&daemon_client),
|
||||
})
|
||||
.forward(sender.input_sender(), |_| AppMsg::None);
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
let view_stack = adw::ViewStack::new();
|
||||
view_stack.add_titled(switch_screen.widget(), None, "Switch");
|
||||
view_stack.add_titled(settings_screen.widget(), None, "Settings");
|
||||
|
||||
let view_switcher_bar = adw::ViewSwitcherBar::builder().stack(&view_stack).build();
|
||||
view_switcher_bar.set_reveal(true);
|
||||
|
||||
let toolbar = adw::ToolbarView::new();
|
||||
toolbar.add_top_bar(
|
||||
&adw::HeaderBar::builder()
|
||||
.title_widget(>k::Label::new(Some("Burrow")))
|
||||
.build(),
|
||||
);
|
||||
toolbar.add_bottom_bar(&view_switcher_bar);
|
||||
toolbar.set_content(Some(&view_stack));
|
||||
|
||||
root.set_content(Some(&toolbar));
|
||||
|
||||
sender.input(AppMsg::PostInit);
|
||||
|
||||
let model = App {
|
||||
daemon_client,
|
||||
switch_screen,
|
||||
_settings_screen: settings_screen,
|
||||
};
|
||||
|
||||
AsyncComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
async fn update(
|
||||
&mut self,
|
||||
_msg: Self::Input,
|
||||
_sender: AsyncComponentSender<Self>,
|
||||
_root: &Self::Root,
|
||||
) {
|
||||
loop {
|
||||
tokio::time::sleep(RECONNECT_POLL_TIME).await;
|
||||
{
|
||||
let mut daemon_client = self.daemon_client.lock().await;
|
||||
let mut disconnected_daemon_client = false;
|
||||
|
||||
if let Some(daemon_client) = daemon_client.as_mut() {
|
||||
if let Err(_e) = daemon_client.send_command(DaemonCommand::ServerInfo).await {
|
||||
disconnected_daemon_client = true;
|
||||
self.switch_screen
|
||||
.emit(switch_screen::SwitchScreenMsg::DaemonDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
if disconnected_daemon_client || daemon_client.is_none() {
|
||||
*daemon_client = DaemonClient::new().await.ok();
|
||||
if daemon_client.is_some() {
|
||||
self.switch_screen
|
||||
.emit(switch_screen::SwitchScreenMsg::DaemonReconnect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
burrow-gtk/src/components/mod.rs
Normal file
20
burrow-gtk/src/components/mod.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use super::*;
|
||||
use adw::prelude::*;
|
||||
use burrow::{DaemonClient, DaemonCommand, DaemonResponseData};
|
||||
use gtk::Align;
|
||||
use relm4::{
|
||||
component::{
|
||||
AsyncComponent, AsyncComponentController, AsyncComponentParts, AsyncComponentSender,
|
||||
AsyncController,
|
||||
},
|
||||
prelude::*,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
mod app;
|
||||
mod settings;
|
||||
mod settings_screen;
|
||||
mod switch_screen;
|
||||
|
||||
pub use app::*;
|
||||
126
burrow-gtk/src/components/settings/diag_group.rs
Normal file
126
burrow-gtk/src/components/settings/diag_group.rs
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
use super::*;
|
||||
use diag::{StatusTernary, SystemSetup};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DiagGroup {
|
||||
daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
||||
|
||||
init_system: SystemSetup,
|
||||
service_installed: StatusTernary,
|
||||
socket_installed: StatusTernary,
|
||||
socket_enabled: StatusTernary,
|
||||
daemon_running: bool,
|
||||
}
|
||||
|
||||
pub struct DiagGroupInit {
|
||||
pub daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
||||
}
|
||||
|
||||
impl DiagGroup {
|
||||
async fn new(daemon_client: Arc<Mutex<Option<DaemonClient>>>) -> Result<Self> {
|
||||
let setup = SystemSetup::new();
|
||||
let daemon_running = daemon_client.lock().await.is_some();
|
||||
|
||||
Ok(Self {
|
||||
service_installed: setup.is_service_installed()?,
|
||||
socket_installed: setup.is_socket_installed()?,
|
||||
socket_enabled: setup.is_socket_enabled()?,
|
||||
daemon_running,
|
||||
init_system: setup,
|
||||
daemon_client,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DiagGroupMsg {
|
||||
Refresh,
|
||||
}
|
||||
|
||||
#[relm4::component(pub, async)]
|
||||
impl AsyncComponent for DiagGroup {
|
||||
type Init = DiagGroupInit;
|
||||
type Input = DiagGroupMsg;
|
||||
type Output = ();
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
#[name(group)]
|
||||
adw::PreferencesGroup {
|
||||
set_title: "Diagnose",
|
||||
set_description: Some("Diagnose Burrow"),
|
||||
|
||||
adw::ActionRow {
|
||||
#[watch]
|
||||
set_title: &format!("Init System: {}", model.init_system)
|
||||
},
|
||||
adw::ActionRow {
|
||||
#[watch]
|
||||
set_title: &format!(
|
||||
"Service installed: {}",
|
||||
status_ternary_to_str(model.service_installed)
|
||||
)
|
||||
},
|
||||
adw::ActionRow {
|
||||
#[watch]
|
||||
set_title: &format!(
|
||||
"Socket installed: {}",
|
||||
status_ternary_to_str(model.socket_installed)
|
||||
)
|
||||
},
|
||||
adw::ActionRow {
|
||||
#[watch]
|
||||
set_title: &format!(
|
||||
"Socket enabled: {}",
|
||||
status_ternary_to_str(model.socket_enabled)
|
||||
)
|
||||
},
|
||||
adw::ActionRow {
|
||||
#[watch]
|
||||
set_title: &format!(
|
||||
"Daemon running: {}",
|
||||
if model.daemon_running { "Yes" } else { "No" }
|
||||
)
|
||||
},
|
||||
gtk::Button {
|
||||
set_label: "Refresh",
|
||||
connect_clicked => DiagGroupMsg::Refresh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn init(
|
||||
init: Self::Init,
|
||||
root: Self::Root,
|
||||
sender: AsyncComponentSender<Self>,
|
||||
) -> AsyncComponentParts<Self> {
|
||||
// Should be impossible to panic here
|
||||
let model = DiagGroup::new(init.daemon_client).await.unwrap();
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
AsyncComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
async fn update(
|
||||
&mut self,
|
||||
msg: Self::Input,
|
||||
_sender: AsyncComponentSender<Self>,
|
||||
_root: &Self::Root,
|
||||
) {
|
||||
match msg {
|
||||
DiagGroupMsg::Refresh => {
|
||||
// Should be impossible to panic here
|
||||
*self = Self::new(Arc::clone(&self.daemon_client)).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn status_ternary_to_str(status: StatusTernary) -> &'static str {
|
||||
match status {
|
||||
StatusTernary::True => "Yes",
|
||||
StatusTernary::False => "No",
|
||||
StatusTernary::NA => "N/A",
|
||||
}
|
||||
}
|
||||
5
burrow-gtk/src/components/settings/mod.rs
Normal file
5
burrow-gtk/src/components/settings/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
use super::*;
|
||||
|
||||
mod diag_group;
|
||||
|
||||
pub use diag_group::{DiagGroup, DiagGroupInit};
|
||||
44
burrow-gtk/src/components/settings_screen.rs
Normal file
44
burrow-gtk/src/components/settings_screen.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
use super::*;
|
||||
|
||||
pub struct SettingsScreen {
|
||||
_diag_group: AsyncController<settings::DiagGroup>,
|
||||
}
|
||||
|
||||
pub struct SettingsScreenInit {
|
||||
pub daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for SettingsScreen {
|
||||
type Init = SettingsScreenInit;
|
||||
type Input = ();
|
||||
type Output = ();
|
||||
|
||||
view! {
|
||||
#[name(preferences)]
|
||||
adw::PreferencesPage {}
|
||||
}
|
||||
|
||||
fn init(
|
||||
init: Self::Init,
|
||||
root: Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let diag_group = settings::DiagGroup::builder()
|
||||
.launch(settings::DiagGroupInit {
|
||||
daemon_client: Arc::clone(&init.daemon_client),
|
||||
})
|
||||
.forward(sender.input_sender(), |_| ());
|
||||
|
||||
let widgets = view_output!();
|
||||
widgets.preferences.add(diag_group.widget());
|
||||
|
||||
let model = SettingsScreen {
|
||||
_diag_group: diag_group,
|
||||
};
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, _: Self::Input, _sender: ComponentSender<Self>) {}
|
||||
}
|
||||
158
burrow-gtk/src/components/switch_screen.rs
Normal file
158
burrow-gtk/src/components/switch_screen.rs
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
use super::*;
|
||||
|
||||
pub struct SwitchScreen {
|
||||
daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
||||
switch: gtk::Switch,
|
||||
switch_screen: gtk::Box,
|
||||
disconnected_banner: adw::Banner,
|
||||
}
|
||||
|
||||
pub struct SwitchScreenInit {
|
||||
pub daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SwitchScreenMsg {
|
||||
DaemonReconnect,
|
||||
DaemonDisconnect,
|
||||
Start,
|
||||
Stop,
|
||||
}
|
||||
|
||||
#[relm4::component(pub, async)]
|
||||
impl AsyncComponent for SwitchScreen {
|
||||
type Init = SwitchScreenInit;
|
||||
type Input = SwitchScreenMsg;
|
||||
type Output = ();
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_valign: Align::BaselineFill,
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
set_margin_all: 5,
|
||||
set_valign: Align::Start,
|
||||
|
||||
#[name(setup_banner)]
|
||||
adw::Banner {
|
||||
set_title: "Burrow is not running!",
|
||||
},
|
||||
},
|
||||
|
||||
#[name(switch_screen)]
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 10,
|
||||
set_margin_all: 5,
|
||||
set_valign: Align::Center,
|
||||
set_vexpand: true,
|
||||
|
||||
gtk::Label {
|
||||
set_label: "Burrow Switch",
|
||||
},
|
||||
|
||||
#[name(switch)]
|
||||
gtk::Switch {
|
||||
set_halign: Align::Center,
|
||||
set_hexpand: false,
|
||||
set_vexpand: false,
|
||||
connect_active_notify => move |switch|
|
||||
sender.input(if switch.is_active() { SwitchScreenMsg::Start } else { SwitchScreenMsg::Stop })
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn init(
|
||||
init: Self::Init,
|
||||
root: Self::Root,
|
||||
sender: AsyncComponentSender<Self>,
|
||||
) -> AsyncComponentParts<Self> {
|
||||
let mut initial_switch_status = false;
|
||||
let mut initial_daemon_server_down = false;
|
||||
|
||||
if let Some(daemon_client) = init.daemon_client.lock().await.as_mut() {
|
||||
if let Ok(res) = daemon_client
|
||||
.send_command(DaemonCommand::ServerInfo)
|
||||
.await
|
||||
.as_ref()
|
||||
{
|
||||
initial_switch_status = match res.result.as_ref() {
|
||||
Ok(DaemonResponseData::None) => false,
|
||||
Ok(DaemonResponseData::ServerInfo(_)) => true,
|
||||
_ => false,
|
||||
};
|
||||
} else {
|
||||
initial_daemon_server_down = true;
|
||||
}
|
||||
} else {
|
||||
initial_daemon_server_down = true;
|
||||
}
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
widgets.switch.set_active(initial_switch_status);
|
||||
|
||||
if initial_daemon_server_down {
|
||||
*init.daemon_client.lock().await = None;
|
||||
widgets.switch.set_active(false);
|
||||
widgets.switch_screen.set_sensitive(false);
|
||||
widgets.setup_banner.set_revealed(true);
|
||||
}
|
||||
|
||||
let model = SwitchScreen {
|
||||
daemon_client: init.daemon_client,
|
||||
switch: widgets.switch.clone(),
|
||||
switch_screen: widgets.switch_screen.clone(),
|
||||
disconnected_banner: widgets.setup_banner.clone(),
|
||||
};
|
||||
|
||||
AsyncComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
async fn update(
|
||||
&mut self,
|
||||
msg: Self::Input,
|
||||
_: AsyncComponentSender<Self>,
|
||||
_root: &Self::Root,
|
||||
) {
|
||||
let mut disconnected_daemon_client = false;
|
||||
|
||||
if let Some(daemon_client) = self.daemon_client.lock().await.as_mut() {
|
||||
match msg {
|
||||
Self::Input::Start => {
|
||||
if let Err(_e) = daemon_client
|
||||
.send_command(DaemonCommand::Start(Default::default()))
|
||||
.await
|
||||
{
|
||||
disconnected_daemon_client = true;
|
||||
}
|
||||
}
|
||||
Self::Input::Stop => {
|
||||
if let Err(_e) = daemon_client.send_command(DaemonCommand::Stop).await {
|
||||
disconnected_daemon_client = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
disconnected_daemon_client = true;
|
||||
}
|
||||
|
||||
if msg == Self::Input::DaemonReconnect {
|
||||
self.disconnected_banner.set_revealed(false);
|
||||
self.switch_screen.set_sensitive(true);
|
||||
}
|
||||
|
||||
if disconnected_daemon_client || msg == Self::Input::DaemonDisconnect {
|
||||
*self.daemon_client.lock().await = None;
|
||||
self.switch.set_active(false);
|
||||
self.switch_screen.set_sensitive(false);
|
||||
self.disconnected_banner.set_revealed(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
burrow-gtk/src/config.rs.in
Normal file
8
burrow-gtk/src/config.rs.in
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#[allow(unused)]
|
||||
pub const ID: &str = @ID@;
|
||||
#[allow(unused)]
|
||||
pub const VERSION: &str = @VERSION@;
|
||||
#[allow(unused)]
|
||||
pub const LOCALEDIR: &str = @LOCALEDIR@;
|
||||
#[allow(unused)]
|
||||
pub const GETTEXT_PACKAGE: &str = @GETTEXT_PACKAGE@;
|
||||
80
burrow-gtk/src/diag.rs
Normal file
80
burrow-gtk/src/diag.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
use super::*;
|
||||
use std::{fmt::Display, fs, process::Command};
|
||||
|
||||
const SYSTEMD_SOCKET_LOC: &str = "/etc/systemd/system/burrow.socket";
|
||||
const SYSTEMD_SERVICE_LOC: &str = "/etc/systemd/system/burrow.service";
|
||||
|
||||
// I don't like this type very much.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum StatusTernary {
|
||||
True,
|
||||
False,
|
||||
NA,
|
||||
}
|
||||
|
||||
// Realistically, we may not explicitly "support" non-systemd platforms which would simply this
|
||||
// code greatly.
|
||||
// Along with replacing [`StatusTernary`] with good old [`bool`].
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum SystemSetup {
|
||||
Systemd,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl SystemSetup {
|
||||
pub fn new() -> Self {
|
||||
if Command::new("systemctl").arg("--version").output().is_ok() {
|
||||
SystemSetup::Systemd
|
||||
} else {
|
||||
SystemSetup::Other
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_service_installed(&self) -> Result<StatusTernary> {
|
||||
match self {
|
||||
SystemSetup::Systemd => Ok(fs::metadata(SYSTEMD_SERVICE_LOC).is_ok().into()),
|
||||
SystemSetup::Other => Ok(StatusTernary::NA),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_socket_installed(&self) -> Result<StatusTernary> {
|
||||
match self {
|
||||
SystemSetup::Systemd => Ok(fs::metadata(SYSTEMD_SOCKET_LOC).is_ok().into()),
|
||||
SystemSetup::Other => Ok(StatusTernary::NA),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_socket_enabled(&self) -> Result<StatusTernary> {
|
||||
match self {
|
||||
SystemSetup::Systemd => {
|
||||
let output = Command::new("systemctl")
|
||||
.arg("is-enabled")
|
||||
.arg("burrow.socket")
|
||||
.output()?
|
||||
.stdout;
|
||||
let output = String::from_utf8(output)?;
|
||||
Ok((output == "enabled\n").into())
|
||||
}
|
||||
SystemSetup::Other => Ok(StatusTernary::NA),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for StatusTernary {
|
||||
fn from(value: bool) -> Self {
|
||||
if value {
|
||||
StatusTernary::True
|
||||
} else {
|
||||
StatusTernary::False
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SystemSetup {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
SystemSetup::Systemd => "Systemd",
|
||||
SystemSetup::Other => "Other",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +1,11 @@
|
|||
use adw::prelude::*;
|
||||
use burrow::{DaemonClient, DaemonCommand, DaemonStartOptions};
|
||||
use gtk::Align;
|
||||
use relm4::{
|
||||
component::{AsyncComponent, AsyncComponentParts, AsyncComponentSender},
|
||||
prelude::*,
|
||||
};
|
||||
use anyhow::Result;
|
||||
|
||||
struct App {}
|
||||
pub mod components;
|
||||
mod diag;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Msg {
|
||||
Start,
|
||||
Stop,
|
||||
}
|
||||
|
||||
#[relm4::component(async)]
|
||||
impl AsyncComponent for App {
|
||||
type Init = ();
|
||||
type Input = Msg;
|
||||
type Output = ();
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
adw::Window {
|
||||
set_title: Some("Simple app"),
|
||||
set_default_size: (640, 480),
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
set_margin_all: 5,
|
||||
set_valign: Align::Center,
|
||||
|
||||
gtk::Label {
|
||||
set_label: "Burrow GTK Switch",
|
||||
},
|
||||
|
||||
gtk::Switch {
|
||||
set_halign: Align::Center,
|
||||
set_hexpand: false,
|
||||
set_vexpand: false,
|
||||
connect_active_notify => move |switch|
|
||||
sender.input(if switch.is_active() { Msg::Start } else { Msg::Stop })
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn init(
|
||||
_: Self::Init,
|
||||
root: Self::Root,
|
||||
sender: AsyncComponentSender<Self>,
|
||||
) -> AsyncComponentParts<Self> {
|
||||
let model = App {};
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
AsyncComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
async fn update(
|
||||
&mut self,
|
||||
msg: Self::Input,
|
||||
_sender: AsyncComponentSender<Self>,
|
||||
_root: &Self::Root,
|
||||
) {
|
||||
match msg {
|
||||
Msg::Start => {
|
||||
let mut client = DaemonClient::new().await.unwrap();
|
||||
client
|
||||
.send_command(DaemonCommand::Start(DaemonStartOptions::default()))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
Msg::Stop => {
|
||||
let mut client = DaemonClient::new().await.unwrap();
|
||||
client.send_command(DaemonCommand::Stop).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Generated using meson
|
||||
mod config;
|
||||
|
||||
fn main() {
|
||||
let app = RelmApp::new("com.hackclub.burrow");
|
||||
app.run_async::<App>(());
|
||||
components::App::run();
|
||||
}
|
||||
|
|
|
|||
34
burrow-gtk/src/meson.build
Normal file
34
burrow-gtk/src/meson.build
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# config.rs.in
|
||||
global_conf = configuration_data()
|
||||
global_conf.set_quoted('ID', app_id)
|
||||
global_conf.set_quoted('VERSION', meson.project_version())
|
||||
global_conf.set_quoted('LOCALEDIR', localedir)
|
||||
global_conf.set_quoted('GETTEXT_PACKAGE', app_name)
|
||||
config = configure_file(
|
||||
input: 'config.rs.in',
|
||||
output: 'config.rs',
|
||||
configuration: global_conf,
|
||||
)
|
||||
|
||||
run_command(
|
||||
'cp',
|
||||
meson.project_build_root() / 'src' / 'config.rs',
|
||||
meson.project_source_root() / 'src',
|
||||
check: true,
|
||||
)
|
||||
|
||||
# Cargo Build
|
||||
cargo_build = custom_target(
|
||||
'cargo-build',
|
||||
build_by_default: true,
|
||||
build_always_stale: true,
|
||||
output: meson.project_name(),
|
||||
console: true,
|
||||
install: true,
|
||||
install_dir: get_option('bindir'),
|
||||
command: [
|
||||
'env', cargo_env,
|
||||
cargo_bin, 'build',
|
||||
cargo_opt, '&&', 'cp', 'target' / rust_target / meson.project_name(), '@OUTPUT@',
|
||||
]
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue