Open .slatepack file with the app #13
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.
|
/// Flag to check if it's first draw.
|
||||||
first_draw: bool,
|
first_draw: bool,
|
||||||
/// Flag to check if attention required after window focus.
|
|
||||||
attention_required: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Platform: PlatformCallbacks> App<Platform> {
|
impl<Platform: PlatformCallbacks> App<Platform> {
|
||||||
|
@ -52,14 +50,13 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||||
platform,
|
platform,
|
||||||
content: Content::default(),
|
content: Content::default(),
|
||||||
resize_direction: None,
|
resize_direction: None,
|
||||||
first_draw: true,
|
first_draw: true
|
||||||
attention_required: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw application content.
|
/// Draw application content.
|
||||||
pub fn ui(&mut self, ctx: &Context) {
|
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 self.first_draw {
|
||||||
if View::is_desktop() {
|
if View::is_desktop() {
|
||||||
self.platform.set_context(ctx);
|
self.platform.set_context(ctx);
|
||||||
|
@ -113,22 +110,17 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide incoming data to wallets.
|
// 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() {
|
if !data.is_empty() {
|
||||||
self.content.wallets.on_data(ui, Some(data), &self.platform);
|
self.content.wallets.on_data(ui, Some(data), &self.platform);
|
||||||
}
|
}
|
||||||
self.attention_required = true;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if desktop window was focused after requested attention.
|
// Check if desktop window was focused after requested attention.
|
||||||
if self.attention_required && View::is_desktop()
|
if self.platform.user_attention_required() &&
|
||||||
&& ctx.input(|i| i.viewport().focused.unwrap_or(true)) {
|
ctx.input(|i| i.viewport().focused.unwrap_or(true)) {
|
||||||
self.attention_required = false;
|
self.platform.clear_user_attention();
|
||||||
ctx.send_viewport_cmd(
|
|
||||||
ViewportCommand::RequestUserAttention(egui::UserAttentionType::Reset)
|
|
||||||
);
|
|
||||||
ctx.send_viewport_cmd(ViewportCommand::WindowLevel(egui::WindowLevel::Normal));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,11 @@ use crate::gui::platform::PlatformCallbacks;
|
||||||
/// Android platform implementation.
|
/// Android platform implementation.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Android {
|
pub struct Android {
|
||||||
|
/// Android related state.
|
||||||
android_app: AndroidApp,
|
android_app: AndroidApp,
|
||||||
|
|
||||||
|
/// Context to repaint content and handle viewport commands.
|
||||||
|
ctx: Arc<RwLock<Option<egui::Context>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Android {
|
impl Android {
|
||||||
|
@ -38,6 +42,7 @@ impl Android {
|
||||||
pub fn new(app: AndroidApp) -> Self {
|
pub fn new(app: AndroidApp) -> Self {
|
||||||
Self {
|
Self {
|
||||||
android_app: app,
|
android_app: app,
|
||||||
|
ctx: Arc::new(RwLock::new(None)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +61,11 @@ impl Android {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlatformCallbacks for 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) {
|
fn show_keyboard(&self) {
|
||||||
// Disable NDK soft input show call before fix for egui.
|
// Disable NDK soft input show call before fix for egui.
|
||||||
// self.android_app.show_soft_input(false);
|
// self.android_app.show_soft_input(false);
|
||||||
|
@ -131,9 +141,12 @@ impl PlatformCallbacks for Android {
|
||||||
cache.push("images");
|
cache.push("images");
|
||||||
std::fs::create_dir_all(cache.to_str().unwrap())?;
|
std::fs::create_dir_all(cache.to_str().unwrap())?;
|
||||||
cache.push(name);
|
cache.push(name);
|
||||||
let mut image = File::create_new(cache.clone()).unwrap();
|
if cache.exists() {
|
||||||
image.write_all(data.as_slice()).unwrap();
|
std::fs::remove_file(cache.clone())?;
|
||||||
image.sync_all().unwrap();
|
}
|
||||||
|
let mut image = File::create_new(cache.clone())?;
|
||||||
|
image.write_all(data.as_slice())?;
|
||||||
|
image.sync_all()?;
|
||||||
// Call share modal at system.
|
// Call share modal at system.
|
||||||
let vm = unsafe { jni::JavaVM::from_raw(self.android_app.vm_as_ptr() as _) }.unwrap();
|
let vm = unsafe { jni::JavaVM::from_raw(self.android_app.vm_as_ptr() as _) }.unwrap();
|
||||||
let env = vm.attach_current_thread().unwrap();
|
let env = vm.attach_current_thread().unwrap();
|
||||||
|
@ -168,9 +181,13 @@ impl PlatformCallbacks for Android {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn consume_data(&mut self) -> Option<String> {
|
fn request_user_attention(&self) {}
|
||||||
None
|
|
||||||
|
fn user_attention_required(&self) -> bool {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clear_user_attention(&self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
|
|
@ -27,10 +27,14 @@ use crate::gui::platform::PlatformCallbacks;
|
||||||
/// Desktop platform related actions.
|
/// Desktop platform related actions.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Desktop {
|
pub struct Desktop {
|
||||||
/// Flag to check if camera stop is needed.
|
|
||||||
stop_camera: Arc<AtomicBool>,
|
|
||||||
/// Context to repaint content and handle viewport commands.
|
/// Context to repaint content and handle viewport commands.
|
||||||
ctx: Arc<RwLock<Option<egui::Context>>>,
|
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 {
|
impl PlatformCallbacks for Desktop {
|
||||||
|
@ -120,38 +124,7 @@ impl PlatformCallbacks for Desktop {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn consume_data(&mut self) -> Option<String> {
|
fn request_user_attention(&self) {
|
||||||
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);
|
|
||||||
|
|
||||||
let r_ctx = self.ctx.read();
|
let r_ctx = self.ctx.read();
|
||||||
if r_ctx.is_some() {
|
if r_ctx.is_some() {
|
||||||
let ctx = r_ctx.as_ref().unwrap();
|
let ctx = r_ctx.as_ref().unwrap();
|
||||||
|
@ -170,6 +143,33 @@ impl Desktop {
|
||||||
}
|
}
|
||||||
ctx.request_repaint();
|
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)]
|
#[allow(dead_code)]
|
||||||
|
@ -255,7 +255,4 @@ impl Desktop {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// Last captured image from started camera.
|
/// Last captured image from started camera.
|
||||||
static ref LAST_CAMERA_IMAGE: Arc<RwLock<Option<(Vec<u8>, u32)>>> = Arc::new(RwLock::new(None));
|
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 share_data(&self, name: String, data: Vec<u8>) -> Result<(), std::io::Error>;
|
||||||
fn pick_file(&self) -> Option<String>;
|
fn pick_file(&self) -> Option<String>;
|
||||||
fn picked_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 eframe::NativeOptions;
|
||||||
use egui::{Context, Stroke};
|
use egui::{Context, Stroke};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
use winit::platform::android::activity::AndroidApp;
|
use winit::platform::android::activity::AndroidApp;
|
||||||
|
@ -256,3 +259,30 @@ fn setup_i18n() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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));
|
||||||
|
}
|
42
src/main.rs
42
src/main.rs
|
@ -41,11 +41,6 @@ fn real_main() {
|
||||||
data = content
|
data = content
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if another app instance already running.
|
|
||||||
if is_app_running(&data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup callback on panic crash.
|
// Setup callback on panic crash.
|
||||||
std::panic::set_hook(Box::new(|info| {
|
std::panic::set_hook(Box::new(|info| {
|
||||||
let backtrace = backtrace::Backtrace::new();
|
let backtrace = backtrace::Backtrace::new();
|
||||||
|
@ -67,7 +62,14 @@ fn real_main() {
|
||||||
|
|
||||||
// Start GUI.
|
// Start GUI.
|
||||||
match std::panic::catch_unwind(|| {
|
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(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => println!("{:?}", e)
|
Err(e) => println!("{:?}", e)
|
||||||
|
@ -77,7 +79,7 @@ fn real_main() {
|
||||||
/// Start GUI with Desktop related setup passing data from opening.
|
/// Start GUI with Desktop related setup passing data from opening.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[cfg(not(target_os = "android"))]
|
#[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 grim::AppConfig;
|
||||||
use dark_light::Mode;
|
use dark_light::Mode;
|
||||||
|
|
||||||
|
@ -127,15 +129,6 @@ fn start_desktop_gui(data: Option<String>) {
|
||||||
eframe::Renderer::Wgpu
|
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.
|
// Start GUI.
|
||||||
let app = grim::gui::App::new(platform.clone());
|
let app = grim::gui::App::new(platform.clone());
|
||||||
match grim::start(options.clone(), grim::app_creator(app)) {
|
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 socket_path = grim::Settings::socket_path();
|
||||||
let name = if GenericNamespaced::is_supported() {
|
let name = if GenericNamespaced::is_supported() {
|
||||||
"grim.sock".to_ns_name::<GenericNamespaced>()?
|
grim::Settings::SOCKET_NAME.to_ns_name::<GenericNamespaced>()?
|
||||||
} else {
|
} else {
|
||||||
socket_path.clone().to_fs_name::<GenericFilePath>()?
|
socket_path.clone().to_fs_name::<GenericFilePath>()?
|
||||||
};
|
};
|
||||||
// Connect to running application socket.
|
// Connect to running application socket.
|
||||||
let conn = Stream::connect(name).await?;
|
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();
|
let (rec, mut sen) = conn.split();
|
||||||
|
|
||||||
// Send data to socket.
|
// Send data to socket.
|
||||||
let data = data.clone().unwrap_or("".to_string());
|
|
||||||
let _ = sen.write_all(data.as_bytes()).await;
|
let _ = sen.write_all(data.as_bytes()).await;
|
||||||
|
|
||||||
drop((rec, sen));
|
drop((rec, sen));
|
||||||
|
@ -201,6 +197,7 @@ fn is_app_running(data: &Option<String>) -> bool {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
fn start_app_socket(platform: grim::gui::platform::Desktop) {
|
fn start_app_socket(platform: grim::gui::platform::Desktop) {
|
||||||
|
std::thread::spawn(move || {
|
||||||
use tor_rtcompat::BlockOn;
|
use tor_rtcompat::BlockOn;
|
||||||
let runtime = tor_rtcompat::tokio::TokioNativeTlsRuntime::create().unwrap();
|
let runtime = tor_rtcompat::tokio::TokioNativeTlsRuntime::create().unwrap();
|
||||||
let _: Result<_, _> = runtime
|
let _: Result<_, _> = runtime
|
||||||
|
@ -213,6 +210,7 @@ fn start_app_socket(platform: grim::gui::platform::Desktop) {
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncBufReadExt, BufReader},
|
io::{AsyncBufReadExt, BufReader},
|
||||||
};
|
};
|
||||||
|
use grim::gui::platform::PlatformCallbacks;
|
||||||
|
|
||||||
// Handle incoming connection.
|
// Handle incoming connection.
|
||||||
async fn handle_conn(conn: Stream)
|
async fn handle_conn(conn: Stream)
|
||||||
|
@ -226,7 +224,7 @@ fn start_app_socket(platform: grim::gui::platform::Desktop) {
|
||||||
|
|
||||||
let socket_path = grim::Settings::socket_path();
|
let socket_path = grim::Settings::socket_path();
|
||||||
let name = if GenericNamespaced::is_supported() {
|
let name = if GenericNamespaced::is_supported() {
|
||||||
"grim.sock".to_ns_name::<GenericNamespaced>()?
|
grim::Settings::SOCKET_NAME.to_ns_name::<GenericNamespaced>()?
|
||||||
} else {
|
} else {
|
||||||
socket_path.clone().to_fs_name::<GenericFilePath>()?
|
socket_path.clone().to_fs_name::<GenericFilePath>()?
|
||||||
};
|
};
|
||||||
|
@ -244,7 +242,6 @@ fn start_app_socket(platform: grim::gui::platform::Desktop) {
|
||||||
x => x?,
|
x => x?,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle connections.
|
|
||||||
loop {
|
loop {
|
||||||
let conn = match listener.accept().await {
|
let conn = match listener.accept().await {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
|
@ -253,13 +250,16 @@ fn start_app_socket(platform: grim::gui::platform::Desktop) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// Handle connection.
|
||||||
let res = handle_conn(conn).await;
|
let res = handle_conn(conn).await;
|
||||||
match res {
|
match res {
|
||||||
Ok(data) => {
|
Ok(data) => {
|
||||||
platform.on_data(data)
|
grim::on_data(data);
|
||||||
|
platform.request_user_attention();
|
||||||
},
|
},
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
Loading…
Reference in a new issue