From d78ec570b08430c78d6606de6007eda5a19735d2 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Thu, 12 Sep 2024 21:27:37 +0300 Subject: [PATCH] platform: passed data at lib, desktop user attention, check existing file on share at android --- src/gui/app.rs | 20 ++--- src/gui/platform/android/mod.rs | 27 ++++-- src/gui/platform/desktop/mod.rs | 71 +++++++-------- src/gui/platform/mod.rs | 4 +- src/lib.rs | 30 +++++++ src/main.rs | 154 ++++++++++++++++---------------- 6 files changed, 172 insertions(+), 134 deletions(-) diff --git a/src/gui/app.rs b/src/gui/app.rs index 92ea0aa..4e1c07a 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -42,8 +42,6 @@ pub struct App { /// Flag to check if it's first draw. first_draw: bool, - /// Flag to check if attention required after window focus. - attention_required: bool, } impl App { @@ -52,14 +50,13 @@ impl App { platform, content: Content::default(), resize_direction: None, - first_draw: true, - attention_required: false, + first_draw: true } } /// Draw application content. pub fn ui(&mut self, ctx: &Context) { - // Set Desktop platform context on first draw. + // Set platform context on first draw. if self.first_draw { if View::is_desktop() { self.platform.set_context(ctx); @@ -113,22 +110,17 @@ impl App { } // Provide incoming data to wallets. - if let Some(data) = self.platform.consume_data() { + if let Some(data) = crate::consume_passed_data() { if !data.is_empty() { self.content.wallets.on_data(ui, Some(data), &self.platform); } - self.attention_required = true; } }); // Check if desktop window was focused after requested attention. - if self.attention_required && View::is_desktop() - && ctx.input(|i| i.viewport().focused.unwrap_or(true)) { - self.attention_required = false; - ctx.send_viewport_cmd( - ViewportCommand::RequestUserAttention(egui::UserAttentionType::Reset) - ); - ctx.send_viewport_cmd(ViewportCommand::WindowLevel(egui::WindowLevel::Normal)); + if self.platform.user_attention_required() && + ctx.input(|i| i.viewport().focused.unwrap_or(true)) { + self.platform.clear_user_attention(); } } diff --git a/src/gui/platform/android/mod.rs b/src/gui/platform/android/mod.rs index 7fbe6ac..fce8afc 100644 --- a/src/gui/platform/android/mod.rs +++ b/src/gui/platform/android/mod.rs @@ -30,7 +30,11 @@ use crate::gui::platform::PlatformCallbacks; /// Android platform implementation. #[derive(Clone)] pub struct Android { + /// Android related state. android_app: AndroidApp, + + /// Context to repaint content and handle viewport commands. + ctx: Arc>>, } impl Android { @@ -38,6 +42,7 @@ impl Android { pub fn new(app: AndroidApp) -> Self { Self { android_app: app, + ctx: Arc::new(RwLock::new(None)), } } @@ -56,6 +61,11 @@ impl Android { } impl PlatformCallbacks for Android { + fn set_context(&mut self, ctx: &egui::Context) { + let mut w_ctx = self.ctx.write(); + *w_ctx = Some(ctx.clone()); + } + fn show_keyboard(&self) { // Disable NDK soft input show call before fix for egui. // self.android_app.show_soft_input(false); @@ -131,9 +141,12 @@ impl PlatformCallbacks for Android { cache.push("images"); std::fs::create_dir_all(cache.to_str().unwrap())?; cache.push(name); - let mut image = File::create_new(cache.clone()).unwrap(); - image.write_all(data.as_slice()).unwrap(); - image.sync_all().unwrap(); + if cache.exists() { + std::fs::remove_file(cache.clone())?; + } + let mut image = File::create_new(cache.clone())?; + image.write_all(data.as_slice())?; + image.sync_all()?; // Call share modal at system. let vm = unsafe { jni::JavaVM::from_raw(self.android_app.vm_as_ptr() as _) }.unwrap(); let env = vm.attach_current_thread().unwrap(); @@ -168,9 +181,13 @@ impl PlatformCallbacks for Android { None } - fn consume_data(&mut self) -> Option { - None + fn request_user_attention(&self) {} + + fn user_attention_required(&self) -> bool { + false } + + fn clear_user_attention(&self) {} } lazy_static! { diff --git a/src/gui/platform/desktop/mod.rs b/src/gui/platform/desktop/mod.rs index dbd266a..bc18707 100644 --- a/src/gui/platform/desktop/mod.rs +++ b/src/gui/platform/desktop/mod.rs @@ -27,10 +27,14 @@ use crate::gui::platform::PlatformCallbacks; /// Desktop platform related actions. #[derive(Clone)] pub struct Desktop { - /// Flag to check if camera stop is needed. - stop_camera: Arc, /// Context to repaint content and handle viewport commands. ctx: Arc>>, + + /// Flag to check if camera stop is needed. + stop_camera: Arc, + + /// Flag to check if attention required after window focusing. + attention_required: Arc, } impl PlatformCallbacks for Desktop { @@ -120,38 +124,7 @@ impl PlatformCallbacks for Desktop { None } - fn consume_data(&mut self) -> Option { - let has_data = { - let r_data = PASSED_DATA.read(); - r_data.is_some() - }; - if has_data { - // Clear data. - let mut w_data = PASSED_DATA.write(); - let data = w_data.clone(); - *w_data = None; - return data; - } - None - } -} - -impl Desktop { - /// Create new instance with provided extra data from app opening. - pub fn new(data: Option) -> Self { - let mut w_data = PASSED_DATA.write(); - *w_data = data; - Self { - stop_camera: Arc::new(AtomicBool::new(false)), - ctx: Arc::new(RwLock::new(None)), - } - } - - /// Handle data passed to application. - pub fn on_data(&self, data: String) { - let mut w_data = PASSED_DATA.write(); - *w_data = Some(data); - + fn request_user_attention(&self) { let r_ctx = self.ctx.read(); if r_ctx.is_some() { let ctx = r_ctx.as_ref().unwrap(); @@ -170,6 +143,33 @@ impl Desktop { } ctx.request_repaint(); } + self.attention_required.store(true, Ordering::Relaxed); + } + + fn user_attention_required(&self) -> bool { + self.attention_required.load(Ordering::Relaxed) + } + + fn clear_user_attention(&self) { + let r_ctx = self.ctx.read(); + if r_ctx.is_some() { + let ctx = r_ctx.as_ref().unwrap(); + ctx.send_viewport_cmd( + ViewportCommand::RequestUserAttention(UserAttentionType::Reset) + ); + ctx.send_viewport_cmd(ViewportCommand::WindowLevel(WindowLevel::Normal)); + } + self.attention_required.store(false, Ordering::Relaxed); + } +} + +impl Desktop { + pub fn new() -> Self { + Self { + stop_camera: Arc::new(AtomicBool::new(false)), + ctx: Arc::new(RwLock::new(None)), + attention_required: Arc::new(AtomicBool::new(false)), + } } #[allow(dead_code)] @@ -255,7 +255,4 @@ impl Desktop { lazy_static! { /// Last captured image from started camera. static ref LAST_CAMERA_IMAGE: Arc, u32)>>> = Arc::new(RwLock::new(None)); - - /// Data passed from deeplink or opened file. - static ref PASSED_DATA: Arc>> = Arc::new(RwLock::new(None)); } diff --git a/src/gui/platform/mod.rs b/src/gui/platform/mod.rs index 06f6a3e..605924e 100644 --- a/src/gui/platform/mod.rs +++ b/src/gui/platform/mod.rs @@ -35,5 +35,7 @@ pub trait PlatformCallbacks { fn share_data(&self, name: String, data: Vec) -> Result<(), std::io::Error>; fn pick_file(&self) -> Option; fn picked_file(&self) -> Option; - fn consume_data(&mut self) -> Option; + fn request_user_attention(&self); + fn user_attention_required(&self) -> bool; + fn clear_user_attention(&self); } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 54ac58b..aba2528 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,9 @@ extern crate rust_i18n; use eframe::NativeOptions; use egui::{Context, Stroke}; +use lazy_static::lazy_static; +use std::sync::Arc; +use parking_lot::RwLock; #[cfg(target_os = "android")] use winit::platform::android::activity::AndroidApp; @@ -255,4 +258,31 @@ fn setup_i18n() { rust_i18n::set_locale(AppConfig::DEFAULT_LOCALE); } } +} + +/// Get data provided from deeplink or opened file. +pub fn consume_passed_data() -> Option { + let has_data = { + let r_data = PASSED_DATA.read(); + r_data.is_some() + }; + if has_data { + // Clear data. + let mut w_data = PASSED_DATA.write(); + let data = w_data.clone(); + *w_data = None; + return data; + } + None +} + +/// Provide data from deeplink or opened file. +pub fn on_data(data: String) { + let mut w_data = PASSED_DATA.write(); + *w_data = Some(data); +} + +lazy_static! { + /// Data provided from deeplink or opened file. + pub static ref PASSED_DATA: Arc>> = Arc::new(RwLock::new(None)); } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 2944c0a..9bb0185 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,11 +41,6 @@ fn real_main() { data = content } - // Check if another app instance already running. - if is_app_running(&data) { - return; - } - // Setup callback on panic crash. std::panic::set_hook(Box::new(|info| { let backtrace = backtrace::Backtrace::new(); @@ -67,7 +62,14 @@ fn real_main() { // Start GUI. match std::panic::catch_unwind(|| { - start_desktop_gui(data); + if is_app_running(&data) { + return; + } else if let Some(data) = data { + grim::on_data(data); + } + let platform = grim::gui::platform::Desktop::new(); + start_app_socket(platform.clone()); + start_desktop_gui(platform); }) { Ok(_) => {} Err(e) => println!("{:?}", e) @@ -77,7 +79,7 @@ fn real_main() { /// Start GUI with Desktop related setup passing data from opening. #[allow(dead_code)] #[cfg(not(target_os = "android"))] -fn start_desktop_gui(data: Option) { +fn start_desktop_gui(platform: grim::gui::platform::Desktop) { use grim::AppConfig; use dark_light::Mode; @@ -127,15 +129,6 @@ fn start_desktop_gui(data: Option) { eframe::Renderer::Wgpu }; - let mut platform = grim::gui::platform::Desktop::new(data); - - // Start app socket at separate thread. - let socket_pl = platform.clone(); - platform = socket_pl.clone(); - std::thread::spawn(move || { - start_app_socket(socket_pl); - }); - // Start GUI. let app = grim::gui::App::new(platform.clone()); match grim::start(options.clone(), grim::app_creator(app)) { @@ -176,16 +169,19 @@ fn is_app_running(data: &Option) -> bool { let socket_path = grim::Settings::socket_path(); let name = if GenericNamespaced::is_supported() { - "grim.sock".to_ns_name::()? + grim::Settings::SOCKET_NAME.to_ns_name::()? } else { socket_path.clone().to_fs_name::()? }; // Connect to running application socket. let conn = Stream::connect(name).await?; + let data = data.clone().unwrap_or("".to_string()); + if data.is_empty() { + return Ok(()); + } let (rec, mut sen) = conn.split(); // Send data to socket. - let data = data.clone().unwrap_or("".to_string()); let _ = sen.write_all(data.as_bytes()).await; drop((rec, sen)); @@ -201,65 +197,69 @@ fn is_app_running(data: &Option) -> bool { #[allow(dead_code)] #[cfg(not(target_os = "android"))] fn start_app_socket(platform: grim::gui::platform::Desktop) { - use tor_rtcompat::BlockOn; - let runtime = tor_rtcompat::tokio::TokioNativeTlsRuntime::create().unwrap(); - let _: Result<_, _> = runtime - .block_on(async { - use interprocess::local_socket::{ - tokio::{prelude::*, Stream}, - GenericFilePath, GenericNamespaced, Listener, ListenerOptions, - }; - use std::io; - use tokio::{ - io::{AsyncBufReadExt, BufReader}, - }; - - // Handle incoming connection. - async fn handle_conn(conn: Stream) - -> io::Result { - let mut read = BufReader::new(&conn); - let mut buffer = String::new(); - // Read data. - let _ = read.read_line(&mut buffer).await; - Ok(buffer) - } - - let socket_path = grim::Settings::socket_path(); - let name = if GenericNamespaced::is_supported() { - "grim.sock".to_ns_name::()? - } else { - socket_path.clone().to_fs_name::()? - }; - if socket_path.exists() { - let _ = std::fs::remove_file(socket_path); - } - - // Create listener. - let opts = ListenerOptions::new().name(name); - let listener = match opts.create_tokio() { - Err(e) if e.kind() == io::ErrorKind::AddrInUse => { - eprintln!("Socket file is occupied."); - return Err::(e); - } - x => x?, - }; - - // Handle connections. - loop { - let conn = match listener.accept().await { - Ok(c) => c, - Err(e) => { - println!("{:?}", e); - continue - } + std::thread::spawn(move || { + use tor_rtcompat::BlockOn; + let runtime = tor_rtcompat::tokio::TokioNativeTlsRuntime::create().unwrap(); + let _: Result<_, _> = runtime + .block_on(async { + use interprocess::local_socket::{ + tokio::{prelude::*, Stream}, + GenericFilePath, GenericNamespaced, Listener, ListenerOptions, }; - let res = handle_conn(conn).await; - match res { - Ok(data) => { - platform.on_data(data) - }, - Err(_) => {} + use std::io; + use tokio::{ + io::{AsyncBufReadExt, BufReader}, + }; + use grim::gui::platform::PlatformCallbacks; + + // Handle incoming connection. + async fn handle_conn(conn: Stream) + -> io::Result { + let mut read = BufReader::new(&conn); + let mut buffer = String::new(); + // Read data. + let _ = read.read_line(&mut buffer).await; + Ok(buffer) } - } - }); + + let socket_path = grim::Settings::socket_path(); + let name = if GenericNamespaced::is_supported() { + grim::Settings::SOCKET_NAME.to_ns_name::()? + } else { + socket_path.clone().to_fs_name::()? + }; + if socket_path.exists() { + let _ = std::fs::remove_file(socket_path); + } + + // Create listener. + let opts = ListenerOptions::new().name(name); + let listener = match opts.create_tokio() { + Err(e) if e.kind() == io::ErrorKind::AddrInUse => { + eprintln!("Socket file is occupied."); + return Err::(e); + } + x => x?, + }; + + loop { + let conn = match listener.accept().await { + Ok(c) => c, + Err(e) => { + println!("{:?}", e); + continue + } + }; + // Handle connection. + let res = handle_conn(conn).await; + match res { + Ok(data) => { + grim::on_data(data); + platform.request_user_attention(); + }, + Err(_) => {} + } + } + }); + }); } \ No newline at end of file