platform: passed data at lib, desktop user attention, check existing file on share at android
This commit is contained in:
parent
dd45f7ce38
commit
d78ec570b0
6 changed files with 172 additions and 134 deletions
|
@ -42,8 +42,6 @@ pub struct App<Platform> {
|
|||
|
||||
/// Flag to check if it's first draw.
|
||||
first_draw: bool,
|
||||
/// Flag to check if attention required after window focus.
|
||||
attention_required: bool,
|
||||
}
|
||||
|
||||
impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
|
@ -52,14 +50,13 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
|||
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<Platform: PlatformCallbacks> App<Platform> {
|
|||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<RwLock<Option<egui::Context>>>,
|
||||
}
|
||||
|
||||
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<String> {
|
||||
None
|
||||
fn request_user_attention(&self) {}
|
||||
|
||||
fn user_attention_required(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn clear_user_attention(&self) {}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
|
|
|
@ -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<AtomicBool>,
|
||||
/// Context to repaint content and handle viewport commands.
|
||||
ctx: Arc<RwLock<Option<egui::Context>>>,
|
||||
|
||||
/// Flag to check if camera stop is needed.
|
||||
stop_camera: Arc<AtomicBool>,
|
||||
|
||||
/// Flag to check if attention required after window focusing.
|
||||
attention_required: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl PlatformCallbacks for Desktop {
|
||||
|
@ -120,38 +124,7 @@ impl PlatformCallbacks for Desktop {
|
|||
None
|
||||
}
|
||||
|
||||
fn consume_data(&mut self) -> Option<String> {
|
||||
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<String>) -> 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<RwLock<Option<(Vec<u8>, u32)>>> = Arc::new(RwLock::new(None));
|
||||
|
||||
/// Data passed from deeplink or opened file.
|
||||
static ref PASSED_DATA: Arc<RwLock<Option<String>>> = Arc::new(RwLock::new(None));
|
||||
}
|
||||
|
|
|
@ -35,5 +35,7 @@ pub trait PlatformCallbacks {
|
|||
fn share_data(&self, name: String, data: Vec<u8>) -> Result<(), std::io::Error>;
|
||||
fn pick_file(&self) -> Option<String>;
|
||||
fn picked_file(&self) -> Option<String>;
|
||||
fn consume_data(&mut self) -> Option<String>;
|
||||
fn request_user_attention(&self);
|
||||
fn user_attention_required(&self) -> bool;
|
||||
fn clear_user_attention(&self);
|
||||
}
|
30
src/lib.rs
30
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<String> {
|
||||
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<RwLock<Option<String>>> = Arc::new(RwLock::new(None));
|
||||
}
|
154
src/main.rs
154
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<String>) {
|
||||
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<String>) {
|
|||
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<String>) -> bool {
|
|||
|
||||
let socket_path = grim::Settings::socket_path();
|
||||
let name = if GenericNamespaced::is_supported() {
|
||||
"grim.sock".to_ns_name::<GenericNamespaced>()?
|
||||
grim::Settings::SOCKET_NAME.to_ns_name::<GenericNamespaced>()?
|
||||
} else {
|
||||
socket_path.clone().to_fs_name::<GenericFilePath>()?
|
||||
};
|
||||
// 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<String>) -> 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<String> {
|
||||
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::<GenericNamespaced>()?
|
||||
} else {
|
||||
socket_path.clone().to_fs_name::<GenericFilePath>()?
|
||||
};
|
||||
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::<Listener, io::Error>(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<String> {
|
||||
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::<GenericNamespaced>()?
|
||||
} else {
|
||||
socket_path.clone().to_fs_name::<GenericFilePath>()?
|
||||
};
|
||||
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::<Listener, io::Error>(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(_) => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue