Initial commit: RustDesk 1.4.7 macOS desktop port
- Filled empty libs/hbb_common/ submodule (cloned from rustdesk/hbb_common) - Patched Flutter 3.44 / Dart 3.12 compatibility: * flutter/lib/generated_bridge.dart: asTypedList with cast<>, DartPort=Int64 * flutter/lib/common.dart: DialogTheme->DialogThemeData, TabBarTheme->TabBarThemeData * flutter/pubspec.yaml: extended_text 14.0.0->15.0.2, google_fonts override 5.0.0 * flutter/macos/Runner/Configs/Release.xcconfig: EXCLUDED_ARCHS=x86_64 - Build verified: cargo check + cargo build --features flutter + cargo build --release --features flutter - Verified flutter build macos --debug and --release both produce working .app - Verified .dmg installer (27MB arm64) created via hdiutil - Build deps: Xcode 26.5, CocoaPods 1.16.2, Flutter 3.44, VCPKG arm64-osx
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
use std::{
|
||||
fs::{self},
|
||||
io::{Cursor, Read},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
const BIN_DATA: &[u8] = include_bytes!("../data.bin");
|
||||
#[cfg(not(windows))]
|
||||
const BIN_DATA: &[u8] = &[];
|
||||
// 4bytes
|
||||
const LENGTH: usize = 4;
|
||||
const IDENTIFIER_LENGTH: usize = 8;
|
||||
const MD5_LENGTH: usize = 32;
|
||||
const BUF_SIZE: usize = 4096;
|
||||
|
||||
pub(crate) struct BinaryData {
|
||||
pub md5_code: &'static [u8],
|
||||
// compressed gzip data
|
||||
pub raw: &'static [u8],
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
pub(crate) struct BinaryReader {
|
||||
pub files: Vec<BinaryData>,
|
||||
pub exe: String,
|
||||
}
|
||||
|
||||
impl Default for BinaryReader {
|
||||
fn default() -> Self {
|
||||
let (files, exe) = BinaryReader::read();
|
||||
Self { files, exe }
|
||||
}
|
||||
}
|
||||
|
||||
impl BinaryData {
|
||||
fn decompress(&self) -> Vec<u8> {
|
||||
let cursor = Cursor::new(self.raw);
|
||||
let mut decoder = brotli::Decompressor::new(cursor, BUF_SIZE);
|
||||
let mut buf = Vec::new();
|
||||
decoder.read_to_end(&mut buf).ok();
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn write_to_file(&self, prefix: &Path) {
|
||||
let p = prefix.join(&self.path);
|
||||
if let Some(parent) = p.parent() {
|
||||
if !parent.exists() {
|
||||
let _ = fs::create_dir_all(parent);
|
||||
}
|
||||
}
|
||||
if p.exists() {
|
||||
// check md5
|
||||
let f = fs::read(p.clone()).unwrap_or_default();
|
||||
let digest = format!("{:x}", md5::compute(&f));
|
||||
let md5_record = String::from_utf8_lossy(self.md5_code);
|
||||
if digest == md5_record {
|
||||
// same, skip this file
|
||||
println!("skip {}", &self.path);
|
||||
return;
|
||||
} else {
|
||||
println!("writing {}", p.display());
|
||||
println!("{} -> {}", md5_record, digest)
|
||||
}
|
||||
}
|
||||
let _ = fs::write(p, self.decompress());
|
||||
}
|
||||
}
|
||||
|
||||
impl BinaryReader {
|
||||
fn read() -> (Vec<BinaryData>, String) {
|
||||
let mut base: usize = 0;
|
||||
let mut parsed = vec![];
|
||||
assert!(BIN_DATA.len() > IDENTIFIER_LENGTH, "bin data invalid!");
|
||||
let mut iden = String::from_utf8_lossy(&BIN_DATA[base..base + IDENTIFIER_LENGTH]);
|
||||
if iden != "rustdesk" {
|
||||
panic!("bin file is not valid!");
|
||||
}
|
||||
base += IDENTIFIER_LENGTH;
|
||||
loop {
|
||||
iden = String::from_utf8_lossy(&BIN_DATA[base..base + IDENTIFIER_LENGTH]);
|
||||
if iden == "rustdesk" {
|
||||
base += IDENTIFIER_LENGTH;
|
||||
break;
|
||||
}
|
||||
// start reading
|
||||
let mut offset = 0;
|
||||
let path_length = u32::from_be_bytes([
|
||||
BIN_DATA[base + offset],
|
||||
BIN_DATA[base + offset + 1],
|
||||
BIN_DATA[base + offset + 2],
|
||||
BIN_DATA[base + offset + 3],
|
||||
]) as usize;
|
||||
offset += LENGTH;
|
||||
let path =
|
||||
String::from_utf8_lossy(&BIN_DATA[base + offset..base + offset + path_length])
|
||||
.to_string();
|
||||
offset += path_length;
|
||||
// file sz
|
||||
let file_length = u32::from_be_bytes([
|
||||
BIN_DATA[base + offset],
|
||||
BIN_DATA[base + offset + 1],
|
||||
BIN_DATA[base + offset + 2],
|
||||
BIN_DATA[base + offset + 3],
|
||||
]) as usize;
|
||||
offset += LENGTH;
|
||||
let raw = &BIN_DATA[base + offset..base + offset + file_length];
|
||||
offset += file_length;
|
||||
// md5
|
||||
let md5 = &BIN_DATA[base + offset..base + offset + MD5_LENGTH];
|
||||
offset += MD5_LENGTH;
|
||||
parsed.push(BinaryData {
|
||||
md5_code: md5,
|
||||
raw: raw,
|
||||
path: path,
|
||||
});
|
||||
base += offset;
|
||||
}
|
||||
// executable
|
||||
let executable = String::from_utf8_lossy(&BIN_DATA[base..]).to_string();
|
||||
(parsed, executable)
|
||||
}
|
||||
|
||||
#[cfg(linux)]
|
||||
pub fn configure_permission(&self, prefix: &Path) {
|
||||
use std::os::unix::prelude::PermissionsExt;
|
||||
|
||||
let exe_path = prefix.join(&self.exe);
|
||||
if exe_path.exists() {
|
||||
if let Ok(f) = File::open(exe_path) {
|
||||
if let Ok(meta) = f.metadata() {
|
||||
let mut permissions = meta.permissions();
|
||||
permissions.set_mode(0o755);
|
||||
f.set_permissions(permissions).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
#![windows_subsystem = "windows"]
|
||||
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use bin_reader::BinaryReader;
|
||||
|
||||
pub mod bin_reader;
|
||||
#[cfg(windows)]
|
||||
mod ui;
|
||||
|
||||
#[cfg(windows)]
|
||||
const APP_METADATA: &[u8] = include_bytes!("../app_metadata.toml");
|
||||
#[cfg(not(windows))]
|
||||
const APP_METADATA: &[u8] = &[];
|
||||
const APP_METADATA_CONFIG: &str = "meta.toml";
|
||||
const META_LINE_PREFIX_TIMESTAMP: &str = "timestamp = ";
|
||||
const APP_PREFIX: &str = "rustdesk";
|
||||
const APPNAME_RUNTIME_ENV_KEY: &str = "RUSTDESK_APPNAME";
|
||||
#[cfg(windows)]
|
||||
const SET_FOREGROUND_WINDOW_ENV_KEY: &str = "SET_FOREGROUND_WINDOW";
|
||||
|
||||
fn is_timestamp_matches(dir: &Path, ts: &mut u64) -> bool {
|
||||
let Ok(app_metadata) = std::str::from_utf8(APP_METADATA) else {
|
||||
return true;
|
||||
};
|
||||
for line in app_metadata.lines() {
|
||||
if line.starts_with(META_LINE_PREFIX_TIMESTAMP) {
|
||||
if let Ok(stored_ts) = line.replace(META_LINE_PREFIX_TIMESTAMP, "").parse::<u64>() {
|
||||
*ts = stored_ts;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if *ts == 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Ok(content) = std::fs::read_to_string(dir.join(APP_METADATA_CONFIG)) {
|
||||
for line in content.lines() {
|
||||
if line.starts_with(META_LINE_PREFIX_TIMESTAMP) {
|
||||
if let Ok(stored_ts) = line.replace(META_LINE_PREFIX_TIMESTAMP, "").parse::<u64>() {
|
||||
return *ts == stored_ts;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn write_meta(dir: &Path, ts: u64) {
|
||||
let meta_file = dir.join(APP_METADATA_CONFIG);
|
||||
if ts != 0 {
|
||||
let content = format!("{}{}", META_LINE_PREFIX_TIMESTAMP, ts);
|
||||
// Ignore is ok here
|
||||
let _ = std::fs::write(meta_file, content);
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(
|
||||
reader: BinaryReader,
|
||||
dir: Option<PathBuf>,
|
||||
clear: bool,
|
||||
_args: &Vec<String>,
|
||||
_ui: &mut bool,
|
||||
) -> Option<PathBuf> {
|
||||
let dir = if let Some(dir) = dir {
|
||||
dir
|
||||
} else {
|
||||
// home dir
|
||||
if let Some(dir) = dirs::data_local_dir() {
|
||||
dir.join(APP_PREFIX)
|
||||
} else {
|
||||
eprintln!("not found data local dir");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let mut ts = 0;
|
||||
if clear || !is_timestamp_matches(&dir, &mut ts) {
|
||||
#[cfg(windows)]
|
||||
if _args.is_empty() {
|
||||
*_ui = true;
|
||||
ui::setup();
|
||||
}
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
for file in reader.files.iter() {
|
||||
file.write_to_file(&dir);
|
||||
}
|
||||
write_meta(&dir, ts);
|
||||
#[cfg(windows)]
|
||||
win::copy_runtime_broker(&dir);
|
||||
#[cfg(linux)]
|
||||
reader.configure_permission(&dir);
|
||||
Some(dir.join(&reader.exe))
|
||||
}
|
||||
|
||||
fn use_null_stdio() -> bool {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// When running in CMD on Windows 7, using Stdio::inherit() with spawn returns an "invalid handle" error.
|
||||
// Since using Stdio::null() didn’t cause any issues, and determining whether the program is launched from CMD or by double-clicking would require calling more APIs during startup, we also use Stdio::null() when launched by double-clicking on Windows 7.
|
||||
let is_windows_7 = is_windows_7();
|
||||
println!("is windows7: {}", is_windows_7);
|
||||
return is_windows_7;
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_windows_7() -> bool {
|
||||
use windows::Wdk::System::SystemServices::RtlGetVersion;
|
||||
use windows::Win32::System::SystemInformation::OSVERSIONINFOW;
|
||||
|
||||
unsafe {
|
||||
let mut version_info = OSVERSIONINFOW::default();
|
||||
version_info.dwOSVersionInfoSize = std::mem::size_of::<OSVERSIONINFOW>() as u32;
|
||||
|
||||
if RtlGetVersion(&mut version_info).is_ok() {
|
||||
// Windows 7 is version 6.1
|
||||
println!(
|
||||
"Windows version: {}.{}",
|
||||
version_info.dwMajorVersion, version_info.dwMinorVersion
|
||||
);
|
||||
return version_info.dwMajorVersion == 6 && version_info.dwMinorVersion == 1;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn execute(path: PathBuf, args: Vec<String>, _ui: bool) {
|
||||
println!("executing {}", path.display());
|
||||
// setup env
|
||||
let exe = std::env::current_exe().unwrap_or_default();
|
||||
let exe_name = exe.file_name().unwrap_or_default();
|
||||
// run executable
|
||||
let mut cmd = Command::new(path);
|
||||
cmd.args(args);
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
cmd.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW);
|
||||
if _ui {
|
||||
cmd.env(SET_FOREGROUND_WINDOW_ENV_KEY, "1");
|
||||
}
|
||||
}
|
||||
|
||||
cmd.env(APPNAME_RUNTIME_ENV_KEY, exe_name);
|
||||
if use_null_stdio() {
|
||||
cmd.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
} else {
|
||||
cmd.stdin(Stdio::inherit())
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit());
|
||||
}
|
||||
let _child = cmd.spawn();
|
||||
|
||||
#[cfg(windows)]
|
||||
if _ui {
|
||||
match _child {
|
||||
Ok(child) => unsafe {
|
||||
winapi::um::winuser::AllowSetForegroundWindow(child.id() as u32);
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut args = Vec::new();
|
||||
let mut arg_exe = Default::default();
|
||||
let mut i = 0;
|
||||
for arg in std::env::args() {
|
||||
if i == 0 {
|
||||
arg_exe = arg.clone();
|
||||
} else {
|
||||
args.push(arg);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
let click_setup = args.is_empty() && arg_exe.to_lowercase().ends_with("install.exe");
|
||||
#[cfg(windows)]
|
||||
let quick_support = args.is_empty() && win::is_quick_support_exe(&arg_exe);
|
||||
#[cfg(not(windows))]
|
||||
let quick_support = false;
|
||||
|
||||
let mut ui = false;
|
||||
let reader = BinaryReader::default();
|
||||
if let Some(exe) = setup(
|
||||
reader,
|
||||
None,
|
||||
click_setup || args.contains(&"--silent-install".to_owned()),
|
||||
&args,
|
||||
&mut ui,
|
||||
) {
|
||||
if click_setup {
|
||||
args = vec!["--install".to_owned()];
|
||||
} else if quick_support {
|
||||
args = vec!["--quick_support".to_owned()];
|
||||
}
|
||||
execute(exe, args, ui);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod win {
|
||||
use std::{fs, os::windows::process::CommandExt, path::Path, process::Command};
|
||||
|
||||
// Used for privacy mode(magnifier impl).
|
||||
pub const RUNTIME_BROKER_EXE: &'static str = "C:\\Windows\\System32\\RuntimeBroker.exe";
|
||||
pub const WIN_TOPMOST_INJECTED_PROCESS_EXE: &'static str = "RuntimeBroker_rustdesk.exe";
|
||||
|
||||
pub(super) fn copy_runtime_broker(dir: &Path) {
|
||||
let src = RUNTIME_BROKER_EXE;
|
||||
let tgt = WIN_TOPMOST_INJECTED_PROCESS_EXE;
|
||||
let target_file = dir.join(tgt);
|
||||
if target_file.exists() {
|
||||
if let (Ok(src_file), Ok(tgt_file)) = (fs::read(src), fs::read(&target_file)) {
|
||||
let src_md5 = format!("{:x}", md5::compute(&src_file));
|
||||
let tgt_md5 = format!("{:x}", md5::compute(&tgt_file));
|
||||
if src_md5 == tgt_md5 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
let _allow_err = Command::new("taskkill")
|
||||
.args(&["/F", "/IM", "RuntimeBroker_rustdesk.exe"])
|
||||
.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW)
|
||||
.output();
|
||||
let _allow_err = std::fs::copy(src, &format!("{}\\{}", dir.to_string_lossy(), tgt));
|
||||
}
|
||||
|
||||
/// Check if the executable is a Quick Support version.
|
||||
/// Note: This function must be kept in sync with `src/core_main.rs`.
|
||||
#[inline]
|
||||
pub(super) fn is_quick_support_exe(exe: &str) -> bool {
|
||||
let exe = exe.to_lowercase();
|
||||
exe.contains("-qs-") || exe.contains("-qs.exe") || exe.contains("_qs.exe")
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
@@ -0,0 +1,232 @@
|
||||
use native_windows_gui as nwg;
|
||||
use nwg::NativeUi;
|
||||
use std::cell::RefCell;
|
||||
|
||||
const GIF_DATA: &[u8] = include_bytes!("./res/spin.gif");
|
||||
const LABEL_DATA: &[u8] = include_bytes!("./res/label.png");
|
||||
const GIF_SIZE: i32 = 32;
|
||||
const BG_COLOR: [u8; 3] = [90, 90, 120];
|
||||
const BORDER_COLOR: [u8; 3] = [40, 40, 40];
|
||||
const GIF_DELAY: u64 = 30;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BasicApp {
|
||||
window: nwg::Window,
|
||||
|
||||
border_image: nwg::ImageFrame,
|
||||
bg_image: nwg::ImageFrame,
|
||||
gif_image: nwg::ImageFrame,
|
||||
label_image: nwg::ImageFrame,
|
||||
|
||||
border_layout: nwg::GridLayout,
|
||||
bg_layout: nwg::GridLayout,
|
||||
inner_layout: nwg::GridLayout,
|
||||
|
||||
timer: nwg::AnimationTimer,
|
||||
decoder: nwg::ImageDecoder,
|
||||
gif_index: RefCell<usize>,
|
||||
gif_images: RefCell<Vec<nwg::Bitmap>>,
|
||||
}
|
||||
|
||||
impl BasicApp {
|
||||
fn exit(&self) {
|
||||
self.timer.stop();
|
||||
nwg::stop_thread_dispatch();
|
||||
}
|
||||
|
||||
fn load_gif(&self) -> Result<(), nwg::NwgError> {
|
||||
let image_source = self.decoder.from_stream(GIF_DATA)?;
|
||||
for frame_index in 0..image_source.frame_count() {
|
||||
let image_data = image_source.frame(frame_index)?;
|
||||
let image_data = self
|
||||
.decoder
|
||||
.resize_image(&image_data, [GIF_SIZE as u32, GIF_SIZE as u32])?;
|
||||
let bmp = image_data.as_bitmap()?;
|
||||
self.gif_images.borrow_mut().push(bmp);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_gif(&self) -> Result<(), nwg::NwgError> {
|
||||
let images = self.gif_images.borrow();
|
||||
if images.len() == 0 {
|
||||
return Err(nwg::NwgError::ImageDecoderError(
|
||||
-1,
|
||||
"no gif images".to_string(),
|
||||
));
|
||||
}
|
||||
let image_index = *self.gif_index.borrow() % images.len();
|
||||
self.gif_image.set_bitmap(Some(&images[image_index]));
|
||||
*self.gif_index.borrow_mut() = (image_index + 1) % images.len();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_timer(&self) {
|
||||
self.timer.start();
|
||||
}
|
||||
}
|
||||
|
||||
mod basic_app_ui {
|
||||
use super::*;
|
||||
use native_windows_gui::{self as nwg, Bitmap};
|
||||
use nwg::{Event, GridLayoutItem};
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct BasicAppUi {
|
||||
inner: Rc<BasicApp>,
|
||||
default_handler: RefCell<Vec<nwg::EventHandler>>,
|
||||
}
|
||||
|
||||
impl nwg::NativeUi<BasicAppUi> for BasicApp {
|
||||
fn build_ui(mut data: BasicApp) -> Result<BasicAppUi, nwg::NwgError> {
|
||||
data.decoder = nwg::ImageDecoder::new()?;
|
||||
let col_cnt: i32 = 7;
|
||||
let row_cnt: i32 = 3;
|
||||
let border_width: i32 = 1;
|
||||
let window_size = (
|
||||
GIF_SIZE * col_cnt + 2 * border_width,
|
||||
GIF_SIZE * row_cnt + 2 * border_width,
|
||||
);
|
||||
|
||||
// Controls
|
||||
nwg::Window::builder()
|
||||
.flags(nwg::WindowFlags::POPUP | nwg::WindowFlags::VISIBLE)
|
||||
.size(window_size)
|
||||
.center(true)
|
||||
.build(&mut data.window)?;
|
||||
|
||||
nwg::ImageFrame::builder()
|
||||
.parent(&data.window)
|
||||
.size(window_size)
|
||||
.background_color(Some(BORDER_COLOR))
|
||||
.build(&mut data.border_image)?;
|
||||
|
||||
nwg::ImageFrame::builder()
|
||||
.parent(&data.border_image)
|
||||
.size((row_cnt * GIF_SIZE, col_cnt * GIF_SIZE))
|
||||
.background_color(Some(BG_COLOR))
|
||||
.build(&mut data.bg_image)?;
|
||||
|
||||
nwg::ImageFrame::builder()
|
||||
.parent(&data.bg_image)
|
||||
.size((GIF_SIZE, GIF_SIZE))
|
||||
.background_color(Some(BG_COLOR))
|
||||
.build(&mut data.gif_image)?;
|
||||
|
||||
nwg::ImageFrame::builder()
|
||||
.parent(&data.bg_image)
|
||||
.background_color(Some(BG_COLOR))
|
||||
.bitmap(Some(&Bitmap::from_bin(LABEL_DATA)?))
|
||||
.build(&mut data.label_image)?;
|
||||
|
||||
nwg::AnimationTimer::builder()
|
||||
.parent(&data.window)
|
||||
.interval(std::time::Duration::from_millis(GIF_DELAY))
|
||||
.build(&mut data.timer)?;
|
||||
|
||||
// Wrap-up
|
||||
let ui = BasicAppUi {
|
||||
inner: Rc::new(data),
|
||||
default_handler: Default::default(),
|
||||
};
|
||||
|
||||
// Layouts
|
||||
nwg::GridLayout::builder()
|
||||
.parent(&ui.window)
|
||||
.spacing(0)
|
||||
.margin([0, 0, 0, 0])
|
||||
.max_column(Some(1))
|
||||
.max_row(Some(1))
|
||||
.child_item(GridLayoutItem::new(&ui.border_image, 0, 0, 1, 1))
|
||||
.build(&ui.border_layout)?;
|
||||
|
||||
nwg::GridLayout::builder()
|
||||
.parent(&ui.border_image)
|
||||
.spacing(0)
|
||||
.margin([
|
||||
border_width as _,
|
||||
border_width as _,
|
||||
border_width as _,
|
||||
border_width as _,
|
||||
])
|
||||
.max_column(Some(1))
|
||||
.max_row(Some(1))
|
||||
.child_item(GridLayoutItem::new(&ui.bg_image, 0, 0, 1, 1))
|
||||
.build(&ui.bg_layout)?;
|
||||
|
||||
nwg::GridLayout::builder()
|
||||
.parent(&ui.bg_image)
|
||||
.spacing(0)
|
||||
.margin([0, 0, 0, 0])
|
||||
.max_column(Some(col_cnt as _))
|
||||
.max_row(Some(row_cnt as _))
|
||||
.child_item(GridLayoutItem::new(&ui.gif_image, 2, 1, 1, 1))
|
||||
.child_item(GridLayoutItem::new(&ui.label_image, 3, 1, 3, 1))
|
||||
.build(&ui.inner_layout)?;
|
||||
|
||||
// Events
|
||||
let evt_ui = Rc::downgrade(&ui.inner);
|
||||
let handle_events = move |evt, _evt_data, _handle| {
|
||||
if let Some(evt_ui) = evt_ui.upgrade().as_mut() {
|
||||
match evt {
|
||||
Event::OnWindowClose => {
|
||||
evt_ui.exit();
|
||||
}
|
||||
Event::OnTimerTick => {
|
||||
if let Err(e) = evt_ui.update_gif() {
|
||||
eprintln!("{:?}", e);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ui.default_handler
|
||||
.borrow_mut()
|
||||
.push(nwg::full_bind_event_handler(
|
||||
&ui.window.handle,
|
||||
handle_events,
|
||||
));
|
||||
|
||||
return Ok(ui);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BasicAppUi {
|
||||
/// To make sure that everything is freed without issues, the default handler must be unbound.
|
||||
fn drop(&mut self) {
|
||||
let mut handlers = self.default_handler.borrow_mut();
|
||||
for handler in handlers.drain(0..) {
|
||||
nwg::unbind_event_handler(&handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for BasicAppUi {
|
||||
type Target = BasicApp;
|
||||
|
||||
fn deref(&self) -> &BasicApp {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ui() -> Result<(), nwg::NwgError> {
|
||||
nwg::init()?;
|
||||
let app = BasicApp::build_ui(Default::default())?;
|
||||
app.load_gif()?;
|
||||
app.start_timer();
|
||||
nwg::dispatch_thread_events();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn setup() {
|
||||
std::thread::spawn(move || {
|
||||
if let Err(e) = ui() {
|
||||
eprintln!("{:?}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user