18
.github/workflows/build.yaml
vendored
@ -97,6 +97,23 @@ jobs:
|
|||||||
args: --release --all-features --target=x86_64-pc-windows-msvc
|
args: --release --all-features --target=x86_64-pc-windows-msvc
|
||||||
use-cross: true
|
use-cross: true
|
||||||
|
|
||||||
|
- name: Install NSIS
|
||||||
|
run: |
|
||||||
|
iwr -useb get.scoop.sh -outfile 'install.ps1'
|
||||||
|
.\install.ps1 -RunAsAdmin
|
||||||
|
scoop update
|
||||||
|
scoop bucket add extras
|
||||||
|
scoop install nsis
|
||||||
|
|
||||||
|
- run: rustup default nightly
|
||||||
|
- run: cargo build --release
|
||||||
|
working-directory: ./ui
|
||||||
|
- run: xcopy /y target\x86_64-pc-windows-msvc\release\*.exe ui\setup\bin\
|
||||||
|
- run: xcopy /y ui\target\release\*.exe ui\setup\
|
||||||
|
- run: mkdir ui\setup\logs
|
||||||
|
- run: makensis /V1 setup.nsi
|
||||||
|
working-directory: ./ui
|
||||||
|
|
||||||
- name: Publish Artifacts
|
- name: Publish Artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
@ -105,6 +122,7 @@ jobs:
|
|||||||
target\x86_64-pc-windows-msvc\release\hbbr.exe
|
target\x86_64-pc-windows-msvc\release\hbbr.exe
|
||||||
target\x86_64-pc-windows-msvc\release\hbbs.exe
|
target\x86_64-pc-windows-msvc\release\hbbs.exe
|
||||||
target\x86_64-pc-windows-msvc\release\rustdesk-utils.exe
|
target\x86_64-pc-windows-msvc\release\rustdesk-utils.exe
|
||||||
|
ui\RustDeskServer.Setup.exe
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
# github (draft) release with all binaries
|
# github (draft) release with all binaries
|
||||||
|
@ -57,3 +57,4 @@ hbb_common = { path = "libs/hbb_common" }
|
|||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["libs/hbb_common"]
|
members = ["libs/hbb_common"]
|
||||||
|
exclude = ["ui"]
|
||||||
|
4
ui/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
|
|
3787
ui/Cargo.lock
generated
Normal file
31
ui/Cargo.toml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
[package]
|
||||||
|
name = "rustdesk_server"
|
||||||
|
version = "0.1.1"
|
||||||
|
description = "rustdesk server gui"
|
||||||
|
authors = ["elilchen"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "1.2", features = [] }
|
||||||
|
winres = "0.1"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-std = { version = "1.12", features = ["attributes", "unstable"] }
|
||||||
|
crossbeam-channel = "0.5"
|
||||||
|
derive-new = "0.5"
|
||||||
|
notify = "5.1"
|
||||||
|
once_cell = "1.17"
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
tauri = { version = "1.2", features = ["fs-exists", "fs-read-dir", "fs-read-file", "fs-write-file", "path-all", "shell-open", "system-tray"] }
|
||||||
|
windows-service = "0.5.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# by default Tauri runs in production mode
|
||||||
|
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
||||||
|
default = ["custom-protocol"]
|
||||||
|
# this feature is used used for production builds where `devPath` points to the filesystem
|
||||||
|
# DO NOT remove this
|
||||||
|
custom-protocol = ["tauri/custom-protocol"]
|
21
ui/build.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build();
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
let mut res = winres::WindowsResource::new();
|
||||||
|
res.set_icon("icons\\icon.ico");
|
||||||
|
res.set_manifest(
|
||||||
|
r#"
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
</assembly>
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
res.compile().unwrap();
|
||||||
|
}
|
||||||
|
}
|
344
ui/html/editor/codemirror.css
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
/* BASICS */
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
/* Set height, width, borders, and global font properties here */
|
||||||
|
font-family: monospace;
|
||||||
|
height: 300px;
|
||||||
|
color: black;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PADDING */
|
||||||
|
|
||||||
|
.CodeMirror-lines {
|
||||||
|
padding: 4px 0; /* Vertical padding around content */
|
||||||
|
}
|
||||||
|
.CodeMirror pre.CodeMirror-line,
|
||||||
|
.CodeMirror pre.CodeMirror-line-like {
|
||||||
|
padding: 0 4px; /* Horizontal padding of content */
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||||
|
background-color: white; /* The little square between H and V scrollbars */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GUTTER */
|
||||||
|
|
||||||
|
.CodeMirror-gutters {
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.CodeMirror-linenumbers {}
|
||||||
|
.CodeMirror-linenumber {
|
||||||
|
padding: 0 3px 0 5px;
|
||||||
|
min-width: 20px;
|
||||||
|
text-align: right;
|
||||||
|
color: #999;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-guttermarker { color: black; }
|
||||||
|
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||||
|
|
||||||
|
/* CURSOR */
|
||||||
|
|
||||||
|
.CodeMirror-cursor {
|
||||||
|
border-left: 1px solid black;
|
||||||
|
border-right: none;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
/* Shown when moving in bi-directional text */
|
||||||
|
.CodeMirror div.CodeMirror-secondarycursor {
|
||||||
|
border-left: 1px solid silver;
|
||||||
|
}
|
||||||
|
.cm-fat-cursor .CodeMirror-cursor {
|
||||||
|
width: auto;
|
||||||
|
border: 0 !important;
|
||||||
|
background: #7e7;
|
||||||
|
}
|
||||||
|
.cm-fat-cursor div.CodeMirror-cursors {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.cm-fat-cursor .CodeMirror-line::selection,
|
||||||
|
.cm-fat-cursor .CodeMirror-line > span::selection,
|
||||||
|
.cm-fat-cursor .CodeMirror-line > span > span::selection { background: transparent; }
|
||||||
|
.cm-fat-cursor .CodeMirror-line::-moz-selection,
|
||||||
|
.cm-fat-cursor .CodeMirror-line > span::-moz-selection,
|
||||||
|
.cm-fat-cursor .CodeMirror-line > span > span::-moz-selection { background: transparent; }
|
||||||
|
.cm-fat-cursor { caret-color: transparent; }
|
||||||
|
@-moz-keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
@keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Can style cursor different in overwrite (non-insert) mode */
|
||||||
|
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||||
|
|
||||||
|
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||||
|
|
||||||
|
.CodeMirror-rulers {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0; top: -50px; bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.CodeMirror-ruler {
|
||||||
|
border-left: 1px solid #ccc;
|
||||||
|
top: 0; bottom: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DEFAULT THEME */
|
||||||
|
|
||||||
|
.cm-s-default .cm-header {color: blue;}
|
||||||
|
.cm-s-default .cm-quote {color: #090;}
|
||||||
|
.cm-negative {color: #d44;}
|
||||||
|
.cm-positive {color: #292;}
|
||||||
|
.cm-header, .cm-strong {font-weight: bold;}
|
||||||
|
.cm-em {font-style: italic;}
|
||||||
|
.cm-link {text-decoration: underline;}
|
||||||
|
.cm-strikethrough {text-decoration: line-through;}
|
||||||
|
|
||||||
|
.cm-s-default .cm-keyword {color: #708;}
|
||||||
|
.cm-s-default .cm-atom {color: #219;}
|
||||||
|
.cm-s-default .cm-number {color: #164;}
|
||||||
|
.cm-s-default .cm-def {color: #00f;}
|
||||||
|
.cm-s-default .cm-variable,
|
||||||
|
.cm-s-default .cm-punctuation,
|
||||||
|
.cm-s-default .cm-property,
|
||||||
|
.cm-s-default .cm-operator {}
|
||||||
|
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||||
|
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
|
||||||
|
.cm-s-default .cm-comment {color: #a50;}
|
||||||
|
.cm-s-default .cm-string {color: #a11;}
|
||||||
|
.cm-s-default .cm-string-2 {color: #f50;}
|
||||||
|
.cm-s-default .cm-meta {color: #555;}
|
||||||
|
.cm-s-default .cm-qualifier {color: #555;}
|
||||||
|
.cm-s-default .cm-builtin {color: #30a;}
|
||||||
|
.cm-s-default .cm-bracket {color: #997;}
|
||||||
|
.cm-s-default .cm-tag {color: #170;}
|
||||||
|
.cm-s-default .cm-attribute {color: #00c;}
|
||||||
|
.cm-s-default .cm-hr {color: #999;}
|
||||||
|
.cm-s-default .cm-link {color: #00c;}
|
||||||
|
|
||||||
|
.cm-s-default .cm-error {color: #f00;}
|
||||||
|
.cm-invalidchar {color: #f00;}
|
||||||
|
|
||||||
|
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||||
|
|
||||||
|
/* Default styles for common addons */
|
||||||
|
|
||||||
|
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
|
||||||
|
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
|
||||||
|
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||||
|
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||||
|
|
||||||
|
/* STOP */
|
||||||
|
|
||||||
|
/* The rest of this file contains styles related to the mechanics of
|
||||||
|
the editor. You probably shouldn't touch them. */
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-scroll {
|
||||||
|
overflow: scroll !important; /* Things will break if this is overridden */
|
||||||
|
/* 50px is the magic margin used to hide the element's real scrollbars */
|
||||||
|
/* See overflow: hidden in .CodeMirror */
|
||||||
|
margin-bottom: -50px; margin-right: -50px;
|
||||||
|
padding-bottom: 50px;
|
||||||
|
height: 100%;
|
||||||
|
outline: none; /* Prevent dragging from highlighting the element */
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
.CodeMirror-sizer {
|
||||||
|
position: relative;
|
||||||
|
border-right: 50px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||||
|
before actual scrolling happens, thus preventing shaking and
|
||||||
|
flickering artifacts. */
|
||||||
|
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 6;
|
||||||
|
display: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.CodeMirror-vscrollbar {
|
||||||
|
right: 0; top: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
.CodeMirror-hscrollbar {
|
||||||
|
bottom: 0; left: 0;
|
||||||
|
overflow-y: hidden;
|
||||||
|
overflow-x: scroll;
|
||||||
|
}
|
||||||
|
.CodeMirror-scrollbar-filler {
|
||||||
|
right: 0; bottom: 0;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-filler {
|
||||||
|
left: 0; bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-gutters {
|
||||||
|
position: absolute; left: 0; top: 0;
|
||||||
|
min-height: 100%;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter {
|
||||||
|
white-space: normal;
|
||||||
|
height: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-bottom: -50px;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 4;
|
||||||
|
background: none !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; bottom: 0;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-elt {
|
||||||
|
position: absolute;
|
||||||
|
cursor: default;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
|
||||||
|
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
|
||||||
|
|
||||||
|
.CodeMirror-lines {
|
||||||
|
cursor: text;
|
||||||
|
min-height: 1px; /* prevents collapsing before first draw */
|
||||||
|
}
|
||||||
|
.CodeMirror pre.CodeMirror-line,
|
||||||
|
.CodeMirror pre.CodeMirror-line-like {
|
||||||
|
/* Reset some styles that the rest of the page might have set */
|
||||||
|
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||||
|
border-width: 0;
|
||||||
|
background: transparent;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre;
|
||||||
|
word-wrap: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
color: inherit;
|
||||||
|
z-index: 2;
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
-webkit-font-variant-ligatures: contextual;
|
||||||
|
font-variant-ligatures: contextual;
|
||||||
|
}
|
||||||
|
.CodeMirror-wrap pre.CodeMirror-line,
|
||||||
|
.CodeMirror-wrap pre.CodeMirror-line-like {
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-linebackground {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0; top: 0; bottom: 0;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-linewidget {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
padding: 0.1px; /* Force widget margins to stay inside of the container */
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-widget {}
|
||||||
|
|
||||||
|
.CodeMirror-rtl pre { direction: rtl; }
|
||||||
|
|
||||||
|
.CodeMirror-code {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force content-box sizing for the elements where we expect it */
|
||||||
|
.CodeMirror-scroll,
|
||||||
|
.CodeMirror-sizer,
|
||||||
|
.CodeMirror-gutter,
|
||||||
|
.CodeMirror-gutters,
|
||||||
|
.CodeMirror-linenumber {
|
||||||
|
-moz-box-sizing: content-box;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-measure {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-cursor {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.CodeMirror-measure pre { position: static; }
|
||||||
|
|
||||||
|
div.CodeMirror-cursors {
|
||||||
|
visibility: hidden;
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
div.CodeMirror-dragcursors {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-focused div.CodeMirror-cursors {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-selected { background: #d9d9d9; }
|
||||||
|
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||||
|
.CodeMirror-crosshair { cursor: crosshair; }
|
||||||
|
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||||
|
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||||
|
|
||||||
|
.cm-searching {
|
||||||
|
background-color: #ffa;
|
||||||
|
background-color: rgba(255, 255, 0, .4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Used to force a border model for a node */
|
||||||
|
.cm-force-border { padding-right: .1px; }
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
/* Hide the cursor when printing */
|
||||||
|
.CodeMirror div.CodeMirror-cursors {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See issue #2901 */
|
||||||
|
.cm-tab-wrap-hack:after { content: ''; }
|
||||||
|
|
||||||
|
/* Help users use markselection to safely style text background */
|
||||||
|
span.CodeMirror-selectedtext { background: none; }
|
9874
ui/html/editor/codemirror.js
Normal file
88
ui/html/editor/toml.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
CodeMirror.defineMode("toml", function () {
|
||||||
|
return {
|
||||||
|
startState: function () {
|
||||||
|
return {
|
||||||
|
inString: false,
|
||||||
|
stringType: "",
|
||||||
|
lhs: true,
|
||||||
|
inArray: 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
token: function (stream, state) {
|
||||||
|
//check for state changes
|
||||||
|
if (!state.inString && ((stream.peek() == '"') || (stream.peek() == "'"))) {
|
||||||
|
state.stringType = stream.peek();
|
||||||
|
stream.next(); // Skip quote
|
||||||
|
state.inString = true; // Update state
|
||||||
|
}
|
||||||
|
if (stream.sol() && state.inArray === 0) {
|
||||||
|
state.lhs = true;
|
||||||
|
}
|
||||||
|
//return state
|
||||||
|
if (state.inString) {
|
||||||
|
while (state.inString && !stream.eol()) {
|
||||||
|
if (stream.peek() === state.stringType) {
|
||||||
|
stream.next(); // Skip quote
|
||||||
|
state.inString = false; // Clear flag
|
||||||
|
} else if (stream.peek() === '\\') {
|
||||||
|
stream.next();
|
||||||
|
stream.next();
|
||||||
|
} else {
|
||||||
|
stream.match(/^.[^\\\"\']*/);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state.lhs ? "property string" : "string"; // Token style
|
||||||
|
} else if (state.inArray && stream.peek() === ']') {
|
||||||
|
stream.next();
|
||||||
|
state.inArray--;
|
||||||
|
return 'bracket';
|
||||||
|
} else if (state.lhs && stream.peek() === '[' && stream.skipTo(']')) {
|
||||||
|
stream.next();//skip closing ]
|
||||||
|
// array of objects has an extra open & close []
|
||||||
|
if (stream.peek() === ']') stream.next();
|
||||||
|
return "atom";
|
||||||
|
} else if (stream.peek() === "#") {
|
||||||
|
stream.skipToEnd();
|
||||||
|
return "comment";
|
||||||
|
} else if (stream.eatSpace()) {
|
||||||
|
return null;
|
||||||
|
} else if (state.lhs && stream.eatWhile(function (c) { return c != '=' && c != ' '; })) {
|
||||||
|
return "property";
|
||||||
|
} else if (state.lhs && stream.peek() === "=") {
|
||||||
|
stream.next();
|
||||||
|
state.lhs = false;
|
||||||
|
return null;
|
||||||
|
} else if (!state.lhs && stream.match(/^\d\d\d\d[\d\-\:\.T]*Z/)) {
|
||||||
|
return 'atom'; //date
|
||||||
|
} else if (!state.lhs && (stream.match('true') || stream.match('false'))) {
|
||||||
|
return 'atom';
|
||||||
|
} else if (!state.lhs && stream.peek() === '[') {
|
||||||
|
state.inArray++;
|
||||||
|
stream.next();
|
||||||
|
return 'bracket';
|
||||||
|
} else if (!state.lhs && stream.match(/^\-?\d+(?:\.\d+)?/)) {
|
||||||
|
return 'number';
|
||||||
|
} else if (!stream.eatSpace()) {
|
||||||
|
stream.next();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineMIME('text/x-toml', 'toml');
|
||||||
|
|
||||||
|
});
|
22
ui/html/index.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>RustDesk Server</title>
|
||||||
|
<link rel="icon" href="data:;base64,=">
|
||||||
|
<script>addEventListener('contextmenu', e => e.preventDefault());</script>
|
||||||
|
<link rel="stylesheet" href="editor/codemirror.css">
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
<script src="editor/codemirror.js"></script>
|
||||||
|
<script src="editor/toml.js"></script>
|
||||||
|
<script type="module" src="/main.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<textarea></textarea>
|
||||||
|
<form>
|
||||||
|
<label><input type="checkbox"> <p>Turn on auto scroll</p></label>
|
||||||
|
<label><p>Press ctrl + s to save</p></label>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
153
ui/html/main.js
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
const { event, fs, path, tauri } = window.__TAURI__;
|
||||||
|
|
||||||
|
class View {
|
||||||
|
constructor() {
|
||||||
|
Object.assign(this, {
|
||||||
|
content: '',
|
||||||
|
action_time: 0,
|
||||||
|
is_auto_scroll: true,
|
||||||
|
is_edit_mode: false,
|
||||||
|
is_file_changed: false,
|
||||||
|
is_form_changed: false,
|
||||||
|
is_content_changed: false
|
||||||
|
}, ...arguments);
|
||||||
|
addEventListener('DOMContentLoaded', this.init.bind(this));
|
||||||
|
}
|
||||||
|
async init() {
|
||||||
|
this.editor = this.renderEditor();
|
||||||
|
this.editor.on('scroll', this.editorScroll.bind(this));
|
||||||
|
this.editor.on('keypress', this.editorSave.bind(this));
|
||||||
|
this.form = this.renderForm();
|
||||||
|
this.form.addEventListener('change', this.formChange.bind(this));
|
||||||
|
event.listen('__update__', this.appAction.bind(this));
|
||||||
|
event.emit('__action__', '__init__');
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
await this.update();
|
||||||
|
this.render();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async update() {
|
||||||
|
if (this.is_file_changed) {
|
||||||
|
this.is_file_changed = false;
|
||||||
|
let now = Date.now(),
|
||||||
|
file = await path.resolveResource(this.file);
|
||||||
|
if (await fs.exists(file)) {
|
||||||
|
let content = await fs.readTextFile(file);
|
||||||
|
if (this.action_time < now) {
|
||||||
|
this.content = content;
|
||||||
|
this.is_content_changed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (now >= this.action_time) {
|
||||||
|
if (this.is_edit_mode) {
|
||||||
|
this.content = `# https://github.com/rustdesk/rustdesk-server#env-variables
|
||||||
|
RUST_LOG=info
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
this.is_content_changed = true;
|
||||||
|
}
|
||||||
|
console.warn(`${this.file} file is missing`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async editorSave(editor, e) {
|
||||||
|
if (e.ctrlKey && e.keyCode === 19 && this.is_edit_mode && !this.locked) {
|
||||||
|
this.locked = true;
|
||||||
|
try {
|
||||||
|
let now = Date.now(),
|
||||||
|
content = this.editor.doc.getValue(),
|
||||||
|
file = await path.resolveResource(this.file);
|
||||||
|
await fs.writeTextFile(file, content);
|
||||||
|
event.emit('__action__', 'restart');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
this.locked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editorScroll(e) {
|
||||||
|
let info = this.editor.getScrollInfo(),
|
||||||
|
distance = info.height - info.top - info.clientHeight,
|
||||||
|
is_end = distance < 1;
|
||||||
|
if (this.is_auto_scroll !== is_end) {
|
||||||
|
this.is_auto_scroll = is_end;
|
||||||
|
this.is_form_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formChange(e) {
|
||||||
|
switch (e.target.tagName.toLowerCase()) {
|
||||||
|
case 'input':
|
||||||
|
this.is_auto_scroll = e.target.checked;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appAction(e) {
|
||||||
|
let [action, data] = e.payload;
|
||||||
|
switch (action) {
|
||||||
|
case 'file':
|
||||||
|
if (data === '.env') {
|
||||||
|
this.is_edit_mode = true;
|
||||||
|
this.file = `bin/${data}`;
|
||||||
|
} else {
|
||||||
|
this.is_edit_mode = false;
|
||||||
|
this.file = `logs/${data}`;
|
||||||
|
}
|
||||||
|
this.action_time = Date.now();
|
||||||
|
this.is_file_changed = true;
|
||||||
|
this.is_form_changed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
if (this.is_form_changed) {
|
||||||
|
this.is_form_changed = false;
|
||||||
|
this.renderForm();
|
||||||
|
}
|
||||||
|
if (this.is_content_changed) {
|
||||||
|
this.is_content_changed = false;
|
||||||
|
this.renderEditor();
|
||||||
|
}
|
||||||
|
if (this.is_auto_scroll && !this.is_edit_mode) {
|
||||||
|
this.renderScrollbar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
renderForm() {
|
||||||
|
let form = this.form || document.querySelector('form'),
|
||||||
|
label = form.querySelectorAll('label'),
|
||||||
|
input = form.querySelector('input');
|
||||||
|
input.checked = this.is_auto_scroll;
|
||||||
|
if (this.is_edit_mode) {
|
||||||
|
label[0].style.display = 'none';
|
||||||
|
label[1].style.display = 'inline';
|
||||||
|
} else {
|
||||||
|
label[0].style.display = 'inline';
|
||||||
|
label[1].style.display = 'none';
|
||||||
|
}
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
renderEditor() {
|
||||||
|
let editor = this.editor || CodeMirror.fromTextArea(document.querySelector('textarea'), {
|
||||||
|
mode: { name: 'toml' },
|
||||||
|
lineNumbers: true,
|
||||||
|
autofocus: true
|
||||||
|
});
|
||||||
|
editor.setOption('readOnly', !this.is_edit_mode);
|
||||||
|
editor.doc.setValue(this.content);
|
||||||
|
editor.doc.clearHistory();
|
||||||
|
this.content = '';
|
||||||
|
editor.focus();
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
renderScrollbar() {
|
||||||
|
let info = this.editor.getScrollInfo();
|
||||||
|
this.editor.scrollTo(info.left, info.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new View();
|
34
ui/html/style.css
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
height: calc(100vh - 20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
height: 20px;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 5px;
|
||||||
|
font-size: 13px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
form>label {
|
||||||
|
display: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
form>label>input,
|
||||||
|
form>label>p {
|
||||||
|
height: 19px;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
BIN
ui/icons/128x128.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
ui/icons/128x128@2x.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
ui/icons/32x32.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
ui/icons/Square107x107Logo.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
ui/icons/Square142x142Logo.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
ui/icons/Square150x150Logo.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
ui/icons/Square284x284Logo.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
ui/icons/Square30x30Logo.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
ui/icons/Square310x310Logo.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
ui/icons/Square44x44Logo.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
ui/icons/Square71x71Logo.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
ui/icons/Square89x89Logo.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
ui/icons/StoreLogo.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
ui/icons/icon.icns
Normal file
BIN
ui/icons/icon.ico
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
ui/icons/icon.png
Normal file
After Width: | Height: | Size: 41 KiB |
108
ui/setup.nsi
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
Unicode true
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Includes
|
||||||
|
|
||||||
|
!include nsDialogs.nsh
|
||||||
|
!include MUI2.nsh
|
||||||
|
!include x64.nsh
|
||||||
|
!include LogicLib.nsh
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# File Info
|
||||||
|
|
||||||
|
!define APP_NAME "RustDeskServer"
|
||||||
|
!define PRODUCT_NAME "rustdesk_server"
|
||||||
|
!define PRODUCT_DESCRIPTION "Installer for ${PRODUCT_NAME}"
|
||||||
|
!define COPYRIGHT "Copyright © 2021"
|
||||||
|
!define VERSION "1.1.7"
|
||||||
|
|
||||||
|
VIProductVersion "${VERSION}.0"
|
||||||
|
VIAddVersionKey "ProductName" "${PRODUCT_NAME}"
|
||||||
|
VIAddVersionKey "ProductVersion" "${VERSION}"
|
||||||
|
VIAddVersionKey "FileDescription" "${PRODUCT_DESCRIPTION}"
|
||||||
|
VIAddVersionKey "LegalCopyright" "${COPYRIGHT}"
|
||||||
|
VIAddVersionKey "FileVersion" "${VERSION}"
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Installer Attributes
|
||||||
|
|
||||||
|
Name "${APP_NAME}"
|
||||||
|
Outfile "${APP_NAME}.Setup.exe"
|
||||||
|
Caption "Setup - ${APP_NAME}"
|
||||||
|
BrandingText "${APP_NAME}"
|
||||||
|
|
||||||
|
ShowInstDetails show
|
||||||
|
RequestExecutionLevel admin
|
||||||
|
SetOverwrite on
|
||||||
|
|
||||||
|
InstallDir "$PROGRAMFILES64\${APP_NAME}"
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Pages
|
||||||
|
|
||||||
|
!define MUI_ICON "icons\icon.ico"
|
||||||
|
!define MUI_ABORTWARNING
|
||||||
|
!define MUI_LANGDLL_ALLLANGUAGES
|
||||||
|
!define MUI_FINISHPAGE_SHOWREADME ""
|
||||||
|
!define MUI_FINISHPAGE_SHOWREADME_TEXT "Create Startup Shortcut"
|
||||||
|
!define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateStartupShortcut
|
||||||
|
!define MUI_FINISHPAGE_RUN "$INSTDIR\${PRODUCT_NAME}.exe"
|
||||||
|
|
||||||
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
|
!insertmacro MUI_PAGE_FINISH
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Language
|
||||||
|
|
||||||
|
!insertmacro MUI_LANGUAGE "English"
|
||||||
|
!insertmacro MUI_LANGUAGE "SimpChinese"
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Sections
|
||||||
|
|
||||||
|
Section "Install"
|
||||||
|
SetShellVarContext all
|
||||||
|
nsExec::Exec 'sc stop hbbr'
|
||||||
|
nsExec::Exec 'sc stop hbbs'
|
||||||
|
nsExec::Exec 'taskkill /F /IM ${PRODUCT_NAME}.exe'
|
||||||
|
Sleep 500 ;
|
||||||
|
|
||||||
|
SetOutPath $INSTDIR
|
||||||
|
File /r "setup\*.*"
|
||||||
|
WriteUninstaller $INSTDIR\uninstall.exe
|
||||||
|
|
||||||
|
CreateDirectory "$SMPROGRAMS\${APP_NAME}"
|
||||||
|
CreateShortCut "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe"
|
||||||
|
CreateShortCut "$SMPROGRAMS\${APP_NAME}\Uninstall.lnk" "$INSTDIR\uninstall.exe"
|
||||||
|
CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe"
|
||||||
|
CreateShortCut "$SMSTARTUP\${APP_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe"
|
||||||
|
|
||||||
|
nsExec::Exec 'netsh advfirewall firewall add rule name="${APP_NAME}" dir=in action=allow program="$INSTDIR\hbbs.exe" enable=yes'
|
||||||
|
nsExec::Exec 'netsh advfirewall firewall add rule name="${APP_NAME}" dir=out action=allow program="$INSTDIR\hbbs.exe" enable=yes'
|
||||||
|
nsExec::Exec 'netsh advfirewall firewall add rule name="${APP_NAME}" dir=in action=allow program="$INSTDIR\hbbr.exe" enable=yes'
|
||||||
|
nsExec::Exec 'netsh advfirewall firewall add rule name="${APP_NAME}" dir=out action=allow program="$INSTDIR\hbbr.exe" enable=yes'
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section "Uninstall"
|
||||||
|
SetShellVarContext all
|
||||||
|
nsExec::Exec 'sc stop hbbr'
|
||||||
|
nsExec::Exec 'sc stop hbbs'
|
||||||
|
nsExec::Exec 'taskkill /F /IM ${PRODUCT_NAME}.exe'
|
||||||
|
Sleep 500 ;
|
||||||
|
|
||||||
|
RMDir /r "$SMPROGRAMS\${APP_NAME}"
|
||||||
|
Delete "$SMSTARTUP\${APP_NAME}.lnk"
|
||||||
|
Delete "$DESKTOP\${APP_NAME}.lnk"
|
||||||
|
nsExec::Exec 'sc delete hbbr'
|
||||||
|
nsExec::Exec 'sc delete hbbs'
|
||||||
|
nsExec::Exec 'netsh advfirewall firewall delete rule name="${APP_NAME}"'
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Functions
|
||||||
|
|
||||||
|
Function CreateStartupShortcut
|
||||||
|
CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe"
|
||||||
|
FunctionEnd
|
BIN
ui/setup/service/nssm.exe
Normal file
23
ui/setup/service/run.cmd
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
@echo off
|
||||||
|
%~d0
|
||||||
|
cd "%~dp0"
|
||||||
|
set nssm="%cd%\nssm"
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
%nssm% install %1 "%cd%\bin\%1.exe"
|
||||||
|
|
||||||
|
%nssm% set %1 DisplayName %1
|
||||||
|
%nssm% set %1 Description rustdesk %1 server
|
||||||
|
%nssm% set %1 Start SERVICE_AUTO_START
|
||||||
|
|
||||||
|
%nssm% set %1 ObjectName LocalSystem
|
||||||
|
%nssm% set %1 Type SERVICE_WIN32_OWN_PROCESS
|
||||||
|
|
||||||
|
%nssm% set %1 AppThrottle 1000
|
||||||
|
%nssm% set %1 AppExit Default Restart
|
||||||
|
%nssm% set %1 AppRestartDelay 0
|
||||||
|
|
||||||
|
%nssm% set %1 AppStdout "%cd%\logs\%1.out"
|
||||||
|
%nssm% set %1 AppStderr "%cd%\logs\%1.err"
|
||||||
|
|
||||||
|
%nssm% start %1
|
5
ui/src/adapter/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pub mod view;
|
||||||
|
pub mod service;
|
||||||
|
|
||||||
|
pub use view::*;
|
||||||
|
pub use service::*;
|
3
ui/src/adapter/service/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod windows;
|
||||||
|
|
||||||
|
pub use windows::*;
|
130
ui/src/adapter/service/windows.rs
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
220
ui/src/adapter/view/desktop.rs
Normal 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()
|
||||||
|
}
|
3
ui/src/adapter/view/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod desktop;
|
||||||
|
|
||||||
|
pub use desktop::*;
|
17
ui/src/lib.rs
Normal 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
@ -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
@ -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::*;
|
59
ui/src/usecase/presenter.rs
Normal 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
@ -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
@ -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
@ -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(())
|
||||||
|
}
|
89
ui/tauri.conf.json
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"beforeDevCommand": "",
|
||||||
|
"beforeBuildCommand": "",
|
||||||
|
"devPath": "html",
|
||||||
|
"distDir": "html",
|
||||||
|
"withGlobalTauri": true
|
||||||
|
},
|
||||||
|
"package": {
|
||||||
|
"productName": "rustdesk_server",
|
||||||
|
"version": "0.1.1"
|
||||||
|
},
|
||||||
|
"tauri": {
|
||||||
|
"allowlist": {
|
||||||
|
"all": false,
|
||||||
|
"fs": {
|
||||||
|
"scope": [
|
||||||
|
"$RESOURCE/bin/.env",
|
||||||
|
"$RESOURCE/logs/*"
|
||||||
|
],
|
||||||
|
"all": false,
|
||||||
|
"exists": true,
|
||||||
|
"readDir": true,
|
||||||
|
"readFile": true,
|
||||||
|
"writeFile": true
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"all": true
|
||||||
|
},
|
||||||
|
"shell": {
|
||||||
|
"all": false,
|
||||||
|
"open": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"category": "DeveloperTool",
|
||||||
|
"copyright": "",
|
||||||
|
"deb": {
|
||||||
|
"depends": []
|
||||||
|
},
|
||||||
|
"externalBin": [],
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
],
|
||||||
|
"identifier": "rustdesk_server",
|
||||||
|
"longDescription": "",
|
||||||
|
"macOS": {
|
||||||
|
"entitlements": null,
|
||||||
|
"exceptionDomain": "",
|
||||||
|
"frameworks": [],
|
||||||
|
"providerShortName": null,
|
||||||
|
"signingIdentity": null
|
||||||
|
},
|
||||||
|
"resources": [],
|
||||||
|
"shortDescription": "",
|
||||||
|
"targets": "all",
|
||||||
|
"windows": {
|
||||||
|
"certificateThumbprint": null,
|
||||||
|
"digestAlgorithm": "sha256",
|
||||||
|
"timestampUrl": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"csp": null
|
||||||
|
},
|
||||||
|
"systemTray": {
|
||||||
|
"iconPath": "icons/icon.ico",
|
||||||
|
"iconAsTemplate": true
|
||||||
|
},
|
||||||
|
"updater": {
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"center": true,
|
||||||
|
"fullscreen": false,
|
||||||
|
"height": 600,
|
||||||
|
"resizable": true,
|
||||||
|
"title": "RustDesk Server",
|
||||||
|
"width": 980
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|