// Copyright 2023 The Grim Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #![windows_subsystem = "windows"] pub fn main() { #[allow(dead_code)] #[cfg(not(target_os = "android"))] real_main(); } #[allow(dead_code)] #[cfg(not(target_os = "android"))] fn real_main() { // Initialize logger. grim::logger::init_logger(); // Handle file path argument passing. let args: Vec<_> = std::env::args().collect(); let mut data = None; if args.len() > 1 { let path = std::path::PathBuf::from(&args[1]); let content = match std::fs::read_to_string(path) { Ok(s) => Some(s), Err(_) => Some(args[1].clone()) }; data = content } // Start GUI. 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); } /// Get panic message from crash payload. #[allow(dead_code)] #[cfg(not(target_os = "android"))] fn panic_info_message<'pi>(panic_info: &'pi std::panic::PanicHookInfo<'_>) -> &'pi str { let payload = panic_info.payload(); // taken from: https://github.com/rust-lang/rust/blob/4b9f4b221b92193c7e95b1beb502c6eb32c3b613/library/std/src/panicking.rs#L194-L200 match payload.downcast_ref::<&'static str>() { Some(msg) => *msg, None => match payload.downcast_ref::() { Some(msg) => msg.as_str(), // Copy what rustc does in the default panic handler None => "Box", }, } } /// Start GUI with Desktop related setup passing data from opening. #[allow(dead_code)] #[cfg(not(target_os = "android"))] fn start_desktop_gui(platform: grim::gui::platform::Desktop) { use grim::AppConfig; let os = egui::os::OperatingSystem::from_target_os(); let (width, height) = AppConfig::window_size(); let mut viewport = egui::ViewportBuilder::default() .with_min_inner_size([AppConfig::MIN_WIDTH, AppConfig::MIN_HEIGHT]) .with_inner_size([width, height]); // Setup icon. if let Ok(icon) = eframe::icon_data::from_png_bytes(include_bytes!("../img/icon.png")) { viewport = viewport.with_icon(std::sync::Arc::new(icon)); } // Setup window position. if let Some((x, y)) = AppConfig::window_pos() { viewport = viewport.with_position(egui::pos2(x, y)); } // Setup window decorations. let is_mac = os == egui::os::OperatingSystem::Mac; let is_win = os == egui::os::OperatingSystem::Windows; viewport = viewport .with_fullsize_content_view(true) .with_window_level(egui::WindowLevel::Normal) .with_title_shown(is_win) .with_titlebar_buttons_shown(is_win) .with_titlebar_shown(is_win) .with_transparent(true) .with_decorations(is_mac || is_win); let mut options = eframe::NativeOptions { renderer: eframe::Renderer::Glow, viewport, ..Default::default() }; // Start GUI. let app = grim::gui::App::new(platform.clone()); match grim::start(options.clone(), grim::app_creator(app)) { Ok(_) => {} Err(_) => { // Start with another renderer on error. options.renderer = eframe::Renderer::Wgpu; let app = grim::gui::App::new(platform); match grim::start(options, grim::app_creator(app)) { Ok(_) => {} Err(e) => { panic!("{}", e); } } } } } /// Check if application is already running to pass data. #[allow(dead_code)] #[cfg(not(target_os = "android"))] fn is_app_running(data: &Option) -> bool { let res: Result<(), Box> = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap() .block_on(async { use interprocess::local_socket::{ tokio::{prelude::*, Stream} }; use tokio::{ io::AsyncWriteExt, }; let socket_path = grim::Settings::socket_path(); let name = socket_name(&socket_path)?; // 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 _ = sen.write_all(data.as_bytes()).await; drop((rec, sen)); Ok(()) }); match res { Ok(_) => true, Err(_) => false } } /// Start desktop socket that handles data for single application instance. #[allow(dead_code)] #[cfg(not(target_os = "android"))] fn start_app_socket(platform: grim::gui::platform::Desktop) { std::thread::spawn(move || { let _ = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap() .block_on(async { use interprocess::local_socket::{ tokio::{prelude::*, Stream}, Listener, ListenerOptions, }; 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) } // Setup socket name. let socket_path = grim::Settings::socket_path(); if socket_path.exists() { let _ = std::fs::remove_file(&socket_path); } let name = socket_name(&socket_path)?; // Create listener. let opts = ListenerOptions::new().name(name); let listener = match opts.create_tokio() { Err(e) if e.kind() == io::ErrorKind::AddrInUse => { log::error!("Socket file is occupied."); return Err::(e); } x => x?, }; loop { let conn = match listener.accept().await { Ok(c) => c, Err(e) => { log::error!("{:?}", e); continue } }; // Handle connection. let res = handle_conn(conn).await; match res { Ok(data) => { grim::on_data(data); platform.request_user_attention(); }, Err(_) => {} } } }); }); } /// Get application socket name from provided path. #[allow(dead_code)] #[cfg(not(target_os = "android"))] fn socket_name(path: &std::path::PathBuf) -> std::io::Result> { use interprocess::local_socket::{NameType, ToFsName, ToNsName}; let name = if egui::os::OperatingSystem::Mac != egui::os::OperatingSystem::from_target_os() && interprocess::local_socket::GenericNamespaced::is_supported() { grim::Settings::SOCKET_NAME.to_ns_name::()? } else { path.clone().to_fs_name::()? }; Ok(name) }