This commit is contained in:
elilchen
2023-02-12 00:48:38 +08:00
parent b2f381913d
commit 7a0e300ff9
44 changed files with 15162 additions and 2 deletions

5
ui/src/adapter/mod.rs Normal file
View File

@@ -0,0 +1,5 @@
pub mod view;
pub mod service;
pub use view::*;
pub use service::*;

View File

@@ -0,0 +1,3 @@
pub mod windows;
pub use windows::*;

View File

@@ -0,0 +1,130 @@
use std::{ffi::OsStr, process::Command};
use crate::{path, usecase::service::*};
use derive_new::new;
use windows_service::{
service::ServiceAccess,
service_manager::{ServiceManager, ServiceManagerAccess},
};
#[derive(Debug, new)]
pub struct WindowsDesktopService {
#[new(value = "DesktopServiceState::Stopped")]
pub state: DesktopServiceState,
}
impl IDesktopService for WindowsDesktopService {
fn start(&mut self) {
call(
[
"echo.",
"%nssm% stop hbbr",
"%nssm% remove hbbr confirm",
"%nssm% stop hbbs",
"%nssm% remove hbbs confirm",
"mkdir logs",
"echo.",
"service\\run.cmd hbbs",
"echo.",
"service\\run.cmd hbbr",
"echo.",
"@ping 127.1 -n 3 >nul",
]
.join(" & "),
);
self.check();
}
fn stop(&mut self) {
call(
[
"echo.",
"%nssm% stop hbbr",
"%nssm% remove hbbr confirm",
"echo.",
"%nssm% stop hbbs",
"%nssm% remove hbbs confirm",
"echo.",
"@ping 127.1 -n 3 >nul",
]
.join(" & "),
);
self.check();
}
fn restart(&mut self) {
nssm(["restart", "hbbs"].map(|x| x.to_owned()));
nssm(["restart", "hbbr"].map(|x| x.to_owned()));
self.check();
}
fn pause(&mut self) {
call(
[
"echo.",
"%nssm% stop hbbr",
"echo.",
"%nssm% stop hbbs",
"echo.",
"@ping 127.1 -n 3 >nul",
]
.join(" & "),
);
self.check();
}
fn check(&mut self) -> DesktopServiceState {
self.state = match service_status("hbbs").as_str() {
"Running" => DesktopServiceState::Started,
// "Stopped" => DeskServerServiceState::Paused,
_ => DesktopServiceState::Stopped,
};
self.state.to_owned()
}
}
fn call(cmd: String) {
Command::new("cmd")
.current_dir(&path())
.env("nssm", "service\\nssm.exe")
.arg("/c")
.arg("start")
.arg("cmd")
.arg("/c")
.arg(cmd)
.output()
.expect("cmd exec error!");
}
fn exec<I, S>(program: S, args: I) -> String
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
match Command::new(program).args(args).output() {
Ok(out) => String::from_utf8(out.stdout).unwrap_or("".to_owned()),
Err(e) => e.to_string(),
}
}
fn nssm<I>(args: I) -> String
where
I: IntoIterator<Item = String>,
{
exec(
format!("{}\\service\\nssm.exe", path().to_str().unwrap_or_default()),
args,
)
.replace("\0", "")
.trim()
.to_owned()
}
fn service_status(name: &str) -> String {
match ServiceManager::local_computer(None::<&OsStr>, ServiceManagerAccess::CONNECT) {
Ok(manager) => match manager.open_service(name, ServiceAccess::QUERY_STATUS) {
Ok(service) => match service.query_status() {
Ok(status) => format!("{:?}", status.current_state),
Err(e) => e.to_string(),
},
Err(e) => e.to_string(),
},
Err(e) => e.to_string(),
}
}

View File

@@ -0,0 +1,220 @@
use std::{
process::exit,
time::{Duration, Instant},
};
use crate::{
path,
usecase::{view::Event, DesktopServiceState},
BUFFER,
};
use async_std::task::sleep;
use crossbeam_channel::{Receiver, Sender};
use tauri::{
CustomMenuItem, Manager, Menu, MenuItem, Submenu, SystemTray, SystemTrayEvent, SystemTrayMenu,
SystemTrayMenuItem, WindowEvent,
};
pub async fn run(sender: Sender<Event>, receiver: Receiver<Event>) {
let setup_sender = sender.clone();
let menu_sender = sender.clone();
let tray_sender = sender.clone();
let menu = Menu::new()
.add_submenu(Submenu::new(
"Service",
Menu::new()
.add_item(CustomMenuItem::new("restart", "Restart"))
.add_native_item(MenuItem::Separator)
.add_item(CustomMenuItem::new("start", "Start"))
.add_item(CustomMenuItem::new("stop", "Stop")),
))
.add_submenu(Submenu::new(
"Logs",
Menu::new()
.add_item(CustomMenuItem::new("hbbs.out", "hbbs.out"))
.add_item(CustomMenuItem::new("hbbs.err", "hbbs.err"))
.add_native_item(MenuItem::Separator)
.add_item(CustomMenuItem::new("hbbr.out", "hbbr.out"))
.add_item(CustomMenuItem::new("hbbr.err", "hbbr.err")),
))
.add_submenu(Submenu::new(
"Configuration",
Menu::new().add_item(CustomMenuItem::new(".env", ".env")),
));
let tray = SystemTray::new().with_menu(
SystemTrayMenu::new()
.add_item(CustomMenuItem::new("restart", "Restart"))
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new("start", "Start"))
.add_item(CustomMenuItem::new("stop", "Stop"))
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new("exit", "Exit GUI")),
);
let mut app = tauri::Builder::default()
.on_window_event(|event| match event.event() {
// WindowEvent::Resized(size) => {
// if size.width == 0 && size.height == 0 {
// event.window().hide().unwrap();
// }
// }
WindowEvent::CloseRequested { api, .. } => {
api.prevent_close();
event.window().hide().unwrap();
}
_ => {}
})
.menu(menu)
.on_menu_event(move |event| {
// println!(
// "send {}: {}",
// std::time::SystemTime::now()
// .duration_since(std::time::UNIX_EPOCH)
// .unwrap_or_default()
// .as_millis(),
// event.menu_item_id()
// );
menu_sender
.send(Event::ViewAction(event.menu_item_id().to_owned()))
.unwrap_or_default()
})
.system_tray(tray)
.on_system_tray_event(move |app, event| match event {
SystemTrayEvent::LeftClick { .. } => {
let main = app.get_window("main").unwrap();
if main.is_visible().unwrap() {
main.hide().unwrap();
} else {
main.show().unwrap();
main.unminimize().unwrap();
main.set_focus().unwrap();
}
}
SystemTrayEvent::MenuItemClick { id, .. } => {
tray_sender.send(Event::ViewAction(id)).unwrap_or_default();
}
_ => {}
})
.setup(move |app| {
setup_sender.send(Event::ViewInit).unwrap_or_default();
app.listen_global("__action__", move |msg| {
match msg.payload().unwrap_or_default() {
r#""__init__""# => setup_sender.send(Event::BroswerInit).unwrap_or_default(),
r#""restart""# => setup_sender
.send(Event::BrowserAction("restart".to_owned()))
.unwrap_or_default(),
_ => (),
}
});
Ok(())
})
.invoke_handler(tauri::generate_handler![root])
.build(tauri::generate_context!())
.expect("error while running tauri application");
let mut now = Instant::now();
let mut blink = false;
let mut span = 0;
let mut title = "".to_owned();
let product = "RustDesk Server";
let buffer = BUFFER.get().unwrap().to_owned();
loop {
for _ in 1..buffer {
match receiver.recv_timeout(Duration::from_nanos(1)) {
Ok(event) => {
let main = app.get_window("main").unwrap();
let menu = main.menu_handle();
let tray = app.tray_handle();
match event {
Event::BrowserUpdate((action, data)) => match action.as_str() {
"file" => {
let list = ["hbbs.out", "hbbs.err", "hbbr.out", "hbbr.err", ".env"];
let id = data.as_str();
if list.contains(&id) {
for file in list {
menu.get_item(file)
.set_selected(file == id)
.unwrap_or_default();
}
// println!(
// "emit {}: {}",
// std::time::SystemTime::now()
// .duration_since(std::time::UNIX_EPOCH)
// .unwrap_or_default()
// .as_millis(),
// data
// );
app.emit_all("__update__", (action, data))
.unwrap_or_default();
}
}
_ => (),
},
Event::ViewRenderAppExit => exit(0),
Event::ViewRenderServiceState(state) => {
let enabled = |id, enabled| {
menu.get_item(id).set_enabled(enabled).unwrap_or_default();
tray.get_item(id).set_enabled(enabled).unwrap_or_default();
};
title = format!("{} {:?}", product, state);
main.set_title(title.as_str()).unwrap_or_default();
match state {
DesktopServiceState::Started => {
enabled("start", false);
enabled("stop", true);
enabled("restart", true);
blink = false;
}
DesktopServiceState::Stopped => {
enabled("start", true);
enabled("stop", false);
enabled("restart", false);
blink = true;
}
_ => {
enabled("start", false);
enabled("stop", false);
enabled("restart", false);
blink = true;
}
}
}
_ => (),
}
}
Err(_) => break,
}
}
let elapsed = now.elapsed().as_micros();
if elapsed > 16666 {
now = Instant::now();
// println!("{}ms", elapsed as f64 * 0.001);
let iteration = app.run_iteration();
if iteration.window_count == 0 {
break;
}
if blink {
if span > 1000000 {
span = 0;
app.get_window("main")
.unwrap()
.set_title(title.as_str())
.unwrap_or_default();
} else {
span += elapsed;
if span > 500000 {
app.get_window("main")
.unwrap()
.set_title(product)
.unwrap_or_default();
}
}
}
} else {
sleep(Duration::from_micros(999)).await;
}
}
}
#[tauri::command]
fn root() -> String {
path().to_str().unwrap_or_default().to_owned()
}

View File

@@ -0,0 +1,3 @@
pub mod desktop;
pub use desktop::*;

17
ui/src/lib.rs Normal file
View File

@@ -0,0 +1,17 @@
use std::{env::current_exe, path::PathBuf};
use once_cell::sync::OnceCell;
pub mod adapter;
pub mod usecase;
pub static BUFFER: OnceCell<usize> = OnceCell::new();
pub fn path() -> PathBuf {
current_exe()
.unwrap_or_default()
.as_path()
.parent()
.unwrap()
.to_owned()
}

25
ui/src/main.rs Normal file
View File

@@ -0,0 +1,25 @@
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
use async_std::{
prelude::FutureExt,
task::{spawn, spawn_local},
};
use crossbeam_channel::bounded;
use rustdesk_server::{
usecase::{presenter, view, watcher},
BUFFER,
};
#[async_std::main]
async fn main() {
let buffer = BUFFER.get_or_init(|| 10).to_owned();
let (view_sender, presenter_receiver) = bounded(buffer);
let (presenter_sender, view_receiver) = bounded(buffer);
spawn_local(view::create(presenter_sender.clone(), presenter_receiver))
.join(spawn(presenter::create(view_sender, view_receiver)))
.join(spawn(watcher::create(presenter_sender)))
.await;
}

9
ui/src/usecase/mod.rs Normal file
View File

@@ -0,0 +1,9 @@
pub mod presenter;
pub mod service;
pub mod view;
pub mod watcher;
pub use presenter::*;
pub use service::*;
pub use view::*;
pub use watcher::*;

View File

@@ -0,0 +1,59 @@
use std::time::{Duration, Instant};
use super::{service, DesktopServiceState, Event};
use crate::BUFFER;
use async_std::task::sleep;
use crossbeam_channel::{Receiver, Sender};
pub async fn create(sender: Sender<Event>, receiver: Receiver<Event>) {
let mut now = Instant::now();
let buffer = BUFFER.get().unwrap().to_owned();
let send = |event| sender.send(event).unwrap_or_default();
if let Some(mut service) = service::create() {
let mut service_state = DesktopServiceState::Unknown;
let mut file = "hbbs.out".to_owned();
send(Event::ViewRenderServiceState(service_state.to_owned()));
loop {
for _ in 1..buffer {
match receiver.recv_timeout(Duration::from_nanos(1)) {
Ok(event) => match event {
Event::BroswerInit => {
send(Event::BrowserUpdate(("file".to_owned(), file.to_owned())));
}
Event::BrowserAction(action) => match action.as_str() {
"restart" => service.restart(),
_ => (),
},
Event::FileChange(path) => {
if path == file {
send(Event::BrowserUpdate(("file".to_owned(), file.to_owned())));
}
}
Event::ViewAction(action) => match action.as_str() {
"start" => service.start(),
"stop" => service.stop(),
"restart" => service.restart(),
"pause" => service.pause(),
"exit" => send(Event::ViewRenderAppExit),
_ => {
file = action;
send(Event::BrowserUpdate(("file".to_owned(), file.to_owned())));
}
},
_ => (),
},
Err(_) => break,
}
}
sleep(Duration::from_micros(999)).await;
if now.elapsed().as_millis() > 999 {
let state = service.check();
if state != service_state {
service_state = state.to_owned();
send(Event::ViewRenderServiceState(state));
}
now = Instant::now();
}
}
}
}

24
ui/src/usecase/service.rs Normal file
View File

@@ -0,0 +1,24 @@
use crate::adapter;
pub fn create() -> Option<Box<dyn IDesktopService + Send>> {
if cfg!(target_os = "windows") {
return Some(Box::new(adapter::WindowsDesktopService::new()));
}
None
}
#[derive(Debug, Clone, PartialEq)]
pub enum DesktopServiceState {
Paused,
Started,
Stopped,
Unknown,
}
pub trait IDesktopService {
fn start(&mut self);
fn stop(&mut self);
fn restart(&mut self);
fn pause(&mut self);
fn check(&mut self) -> DesktopServiceState;
}

22
ui/src/usecase/view.rs Normal file
View File

@@ -0,0 +1,22 @@
use super::DesktopServiceState;
use crate::adapter::desktop;
use crossbeam_channel::{Receiver, Sender};
pub async fn create(sender: Sender<Event>, receiver: Receiver<Event>) {
desktop::run(sender, receiver).await;
}
#[derive(Debug, Clone, PartialEq)]
pub enum Event {
BrowserAction(String),
BroswerInit,
BrowserUpdate((String, String)),
BrowserRender(String),
FileChange(String),
ViewAction(String),
ViewInit,
ViewUpdate(String),
ViewRender(String),
ViewRenderAppExit,
ViewRenderServiceState(DesktopServiceState),
}

46
ui/src/usecase/watcher.rs Normal file
View File

@@ -0,0 +1,46 @@
use std::{path::Path, time::Duration};
use super::Event;
use crate::path;
use async_std::task::{sleep, spawn_blocking};
use crossbeam_channel::{bounded, Sender};
use notify::{Config, RecommendedWatcher, RecursiveMode, Result, Watcher};
pub async fn create(sender: Sender<Event>) {
loop {
let watch_sender = sender.clone();
match spawn_blocking(|| {
watch(
format!("{}/logs/", path().to_str().unwrap_or_default()),
watch_sender,
)
})
.await
{
Ok(_) => (),
Err(e) => println!("error: {e}"),
}
sleep(Duration::from_secs(1)).await;
}
}
fn watch<P: AsRef<Path>>(path: P, sender: Sender<Event>) -> Result<()> {
let (tx, rx) = bounded(10);
let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
watcher.watch(path.as_ref(), RecursiveMode::Recursive)?;
for res in rx {
let event = res?;
for p in event.paths {
let path = p
.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or_default()
.to_owned();
if path.len() > 0 {
sender.send(Event::FileChange(path)).unwrap_or_default();
}
}
}
Ok(())
}