├── .github ├── FUNDING.yml └── workflows │ ├── check_and_lint.yml │ └── build_test.yml ├── rustfmt.toml ├── nativeshell ├── src │ ├── shell │ │ ├── platform │ │ │ ├── null │ │ │ │ ├── drag_data.rs │ │ │ │ ├── init.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── error.rs │ │ │ │ ├── app_delegate.rs │ │ │ │ ├── keyboard_map.rs │ │ │ │ ├── engine.rs │ │ │ │ ├── binary_messenger.rs │ │ │ │ ├── run_loop.rs │ │ │ │ ├── hot_key.rs │ │ │ │ ├── menu.rs │ │ │ │ └── window.rs │ │ │ ├── macos │ │ │ │ ├── init.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── error.rs │ │ │ │ ├── bundle.rs │ │ │ │ ├── keyboard_map_sys.rs │ │ │ │ ├── engine.rs │ │ │ │ ├── hot_key_sys.rs │ │ │ │ └── binary_messenger.rs │ │ │ ├── linux │ │ │ │ ├── mod.rs │ │ │ │ ├── app_delegate.rs │ │ │ │ ├── init.rs │ │ │ │ ├── hot_key.rs │ │ │ │ ├── error.rs │ │ │ │ ├── engine.rs │ │ │ │ ├── binary_messenger.rs │ │ │ │ ├── run_loop.rs │ │ │ │ ├── utils.rs │ │ │ │ ├── size_widget.rs │ │ │ │ ├── flutter_sys.rs │ │ │ │ └── menu_item.rs │ │ │ ├── win32 │ │ │ │ ├── app_delegate.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── init.rs │ │ │ │ ├── error.rs │ │ │ │ ├── dpi.rs │ │ │ │ ├── engine.rs │ │ │ │ ├── hot_key.rs │ │ │ │ └── util.rs │ │ │ └── mod.rs │ │ ├── macros.rs │ │ ├── handle.rs │ │ ├── bundle.rs │ │ ├── mod.rs │ │ ├── engine.rs │ │ ├── binary_messenger.rs │ │ ├── keyboard_map_manager.rs │ │ ├── hot_key_manager.rs │ │ ├── method_call_handler.rs │ │ ├── event_channel.rs │ │ ├── run_loop.rs │ │ └── engine_manager.rs │ ├── util │ │ ├── mod.rs │ │ ├── log.rs │ │ ├── errno.rs │ │ ├── future.rs │ │ ├── diff.rs │ │ ├── cell.rs │ │ └── capsule.rs │ ├── lib.rs │ ├── error.rs │ └── codec │ │ ├── mod.rs │ │ ├── message_channel.rs │ │ └── method_channel.rs ├── README.md ├── build.rs ├── LICENSE ├── Cargo.toml └── keyboard_map │ └── gen_keyboard_map.rs ├── nativeshell_dart ├── analysis_options.yaml ├── lib │ ├── accelerators.dart │ ├── src │ │ ├── shader_warmup.dart │ │ ├── util.dart │ │ ├── mutex.dart │ │ ├── event.dart │ │ ├── accelerators.dart │ │ ├── key_interceptor.dart │ │ ├── keyboard_map_internal.dart │ │ ├── intrinsic_sized_box.dart │ │ ├── keyboard_map.dart │ │ ├── menu_bar.dart │ │ ├── api_constants.dart │ │ ├── api_model_internal.dart │ │ ├── drag_session.dart │ │ ├── hot_key.dart │ │ ├── menu.dart │ │ └── accelerator.dart │ └── nativeshell.dart ├── README.md ├── CHANGELOG.md ├── pubspec.yaml └── LICENSE ├── Cargo.toml ├── nativeshell_build ├── README.md ├── src │ ├── lib.rs │ ├── res │ │ ├── linux │ │ │ └── CMakeLists.txt │ │ ├── macos │ │ │ └── Info.plist │ │ └── windows │ │ │ └── CMakeLists.txt │ ├── resources.rs │ ├── plugins_linux.rs │ ├── plugins_windows.rs │ └── error.rs ├── Cargo.toml └── LICENSE ├── .gitignore └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: knopp 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_field_init_shorthand=true 2 | imports_granularity="Crate" 3 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/null/drag_data.rs: -------------------------------------------------------------------------------- 1 | pub trait DragDataAdapter {} 2 | -------------------------------------------------------------------------------- /nativeshell_dart/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/accelerators.dart: -------------------------------------------------------------------------------- 1 | library nativeshell; 2 | 3 | export 'src/accelerators.dart'; 4 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/null/init.rs: -------------------------------------------------------------------------------- 1 | use super::error::PlatformResult; 2 | 3 | pub fn init_platform() -> PlatformResult<()> { 4 | Ok(()) 5 | } 6 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/macos/init.rs: -------------------------------------------------------------------------------- 1 | use super::error::PlatformResult; 2 | 3 | pub fn init_platform() -> PlatformResult<()> { 4 | Ok(()) 5 | } 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "nativeshell_build", 5 | "nativeshell", 6 | ] 7 | 8 | exclude = [ 9 | "build_test/project1", 10 | "build_test/project2", 11 | ] 12 | -------------------------------------------------------------------------------- /nativeshell_build/README.md: -------------------------------------------------------------------------------- 1 | # NativeShell Build Support 2 | 3 | Please see [nativeshell.dev](https://nativeshell.dev) or the 4 | [github page](https://github.com/nativeshell/nativeshell) for more information. 5 | -------------------------------------------------------------------------------- /nativeshell/README.md: -------------------------------------------------------------------------------- 1 | # NativeShell Rust Crate 2 | 3 | ## Getting Started 4 | 5 | Please see [nativeshell.dev](https://nativeshell.dev) or the 6 | [github page](https://github.com/nativeshell/nativeshell) for more information. 7 | -------------------------------------------------------------------------------- /nativeshell/src/util/mod.rs: -------------------------------------------------------------------------------- 1 | mod capsule; 2 | mod cell; 3 | mod diff; 4 | pub mod errno; 5 | mod future; 6 | mod log; 7 | 8 | pub use self::{diff::*, log::*}; 9 | pub use capsule::*; 10 | pub use cell::*; 11 | pub use future::*; 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /build_test/project1/target 3 | /build_test/project2/target 4 | .dart_tool 5 | .cache 6 | .DS_Store 7 | .packages 8 | build 9 | Cargo.lock 10 | pubspec.lock 11 | .flutter-plugins 12 | .flutter-plugins-dependencies 13 | -------------------------------------------------------------------------------- /nativeshell/src/shell/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! include_flutter_plugins { 3 | () => { 4 | ::std::include!(::std::concat!( 5 | ::std::env!("OUT_DIR"), 6 | "/generated_plugins_registrar.rs" 7 | )); 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /nativeshell_build/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod artifacts_emitter; 2 | mod error; 3 | mod flutter_build; 4 | mod macos_bundle; 5 | mod plugins; 6 | mod resources; 7 | mod util; 8 | 9 | pub use error::*; 10 | pub use flutter_build::*; 11 | pub use macos_bundle::*; 12 | pub use resources::*; 13 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/null/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod app_delegate; 2 | pub mod binary_messenger; 3 | pub mod drag_data; 4 | pub mod engine; 5 | pub mod error; 6 | pub mod hot_key; 7 | pub mod init; 8 | pub mod keyboard_map; 9 | pub mod menu; 10 | pub mod run_loop; 11 | pub mod window; 12 | -------------------------------------------------------------------------------- /nativeshell/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::new_without_default)] 2 | #![allow(clippy::manual_range_contains)] 3 | #![allow(clippy::type_complexity)] 4 | 5 | pub mod codec; 6 | pub mod shell; 7 | pub mod util; 8 | 9 | mod error; 10 | pub use error::*; 11 | 12 | pub use shell::{spawn, Context}; 13 | -------------------------------------------------------------------------------- /nativeshell_dart/README.md: -------------------------------------------------------------------------------- 1 | # NativeShell Dart API 2 | 3 | ## Getting Started 4 | 5 | Please see [nativeshell.dev](https://nativeshell.dev) or the 6 | [github page](https://github.com/nativeshell/nativeshell) for more information. 7 | 8 | ![](https://nativeshell.dev/screenshot-dev.png "Screenshot") 9 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/src/shader_warmup.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/rendering.dart'; 2 | 3 | void disableShaderWarmUp() { 4 | PaintingBinding.shaderWarmUp = _DummyWarmup(); 5 | } 6 | 7 | class _DummyWarmup extends ShaderWarmUp { 8 | @override 9 | Future execute() async {} 10 | 11 | @override 12 | Future warmUpOnCanvas(Canvas canvas) async {} 13 | } 14 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/src/util.dart: -------------------------------------------------------------------------------- 1 | String enumToString(T enumItem) { 2 | return enumItem.toString().split('.')[1]; 3 | } 4 | 5 | T enumFromString( 6 | List enumValues, 7 | String value, 8 | T defaultValue, 9 | ) { 10 | // ignore: unnecessary_cast 11 | return enumValues.singleWhere( 12 | (enumItem) => enumToString(enumItem).toLowerCase() == value.toLowerCase(), 13 | orElse: () => defaultValue); 14 | } 15 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/linux/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod app_delegate; 2 | pub mod binary_messenger; 3 | pub mod drag_context; 4 | pub mod drag_data; 5 | pub mod engine; 6 | pub mod error; 7 | pub mod flutter; 8 | pub mod flutter_sys; 9 | pub mod hot_key; 10 | pub mod init; 11 | pub mod keyboard_map; 12 | pub mod menu; 13 | pub mod menu_item; 14 | pub mod run_loop; 15 | pub mod size_widget; 16 | pub mod utils; 17 | pub mod window; 18 | pub mod window_menu; 19 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/macos/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod app_delegate; 2 | pub mod binary_messenger; 3 | pub mod bundle; 4 | mod drag_context; 5 | pub mod drag_data; 6 | pub mod engine; 7 | pub mod error; 8 | pub mod hot_key; 9 | pub mod hot_key_sys; 10 | pub mod init; 11 | pub mod keyboard_map; 12 | pub mod menu; 13 | pub mod run_loop; 14 | mod utils; 15 | pub mod window; 16 | 17 | #[allow(dead_code)] 18 | #[allow(non_upper_case_globals)] 19 | mod keyboard_map_sys; 20 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/null/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | #[derive(Debug, Clone)] 4 | pub enum PlatformError { 5 | NotImplemented, 6 | UnknownError, 7 | } 8 | 9 | pub type PlatformResult = Result; 10 | 11 | impl Display for PlatformError { 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | write!(f, "{:?}", self) 14 | } 15 | } 16 | 17 | impl std::error::Error for PlatformError {} 18 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/nativeshell.dart: -------------------------------------------------------------------------------- 1 | library nativeshell; 2 | 3 | export 'src/accelerator.dart'; 4 | export 'src/api_model.dart'; 5 | export 'src/drag_drop.dart'; 6 | export 'src/drag_session.dart'; 7 | export 'src/hot_key.dart'; 8 | export 'src/intrinsic_sized_box.dart'; 9 | export 'src/keyboard_map.dart'; 10 | export 'src/menu_bar.dart'; 11 | export 'src/menu.dart'; 12 | export 'src/shader_warmup.dart'; 13 | export 'src/window_widget.dart'; 14 | export 'src/window.dart'; 15 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/linux/app_delegate.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use crate::shell::ContextRef; 4 | 5 | pub trait ApplicationDelegate {} 6 | 7 | pub struct ApplicationDelegateManager {} 8 | 9 | impl ApplicationDelegateManager { 10 | pub fn new(_context: &ContextRef) -> Self { 11 | Self {} 12 | } 13 | pub fn set_delegate(&self, _delegate: D) {} 14 | pub fn set_delegate_ref(&self, _delegate: Rc>) {} 15 | } 16 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/null/app_delegate.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use crate::shell::ContextRef; 4 | 5 | pub trait ApplicationDelegate {} 6 | 7 | pub struct ApplicationDelegateManager {} 8 | 9 | impl ApplicationDelegateManager { 10 | pub fn new(_context: &ContextRef) -> Self { 11 | Self {} 12 | } 13 | pub fn set_delegate(&self, _delegate: D) {} 14 | pub fn set_delegate_ref(&self, _delegate: Rc>) {} 15 | } 16 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/win32/app_delegate.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use crate::shell::ContextRef; 4 | 5 | pub trait ApplicationDelegate {} 6 | 7 | pub struct ApplicationDelegateManager {} 8 | 9 | impl ApplicationDelegateManager { 10 | pub fn new(_context: &ContextRef) -> Self { 11 | Self {} 12 | } 13 | pub fn set_delegate(&self, _delegate: D) {} 14 | pub fn set_delegate_ref(&self, _delegate: Rc>) {} 15 | } 16 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/win32/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod app_delegate; 2 | pub mod binary_messenger; 3 | pub mod display; 4 | pub mod dpi; 5 | pub mod drag_com; 6 | pub mod drag_context; 7 | pub mod drag_data; 8 | pub mod drag_util; 9 | pub mod dxgi_hook; 10 | pub mod engine; 11 | pub mod error; 12 | pub mod flutter_sys; 13 | pub mod hot_key; 14 | pub mod init; 15 | pub mod keyboard_map; 16 | pub mod menu; 17 | pub mod run_loop; 18 | pub mod util; 19 | pub mod window; 20 | pub mod window_adapter; 21 | pub mod window_base; 22 | pub mod window_menu; 23 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/null/keyboard_map.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Weak}; 2 | 3 | use crate::shell::{api_model::KeyboardMap, Context, KeyboardMapDelegate}; 4 | 5 | pub struct PlatformKeyboardMap {} 6 | 7 | impl PlatformKeyboardMap { 8 | pub fn new(context: Context, delegate: Weak>) -> Self { 9 | Self {} 10 | } 11 | 12 | pub fn get_current_map(&self) -> KeyboardMap { 13 | KeyboardMap { keys: vec![] } 14 | } 15 | 16 | pub fn assign_weak_self(&self, weak: Weak) {} 17 | } 18 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::platform_impl::*; 2 | 3 | // #[path = "null/mod.rs"] 4 | // mod platform_impl; 5 | 6 | #[cfg(target_os = "macos")] 7 | #[path = "macos/mod.rs"] 8 | mod platform_impl; 9 | 10 | #[cfg(target_os = "windows")] 11 | #[path = "win32/mod.rs"] 12 | mod platform_impl; 13 | 14 | #[cfg(target_os = "linux")] 15 | #[path = "linux/mod.rs"] 16 | mod platform_impl; 17 | 18 | // Null implementation - include just to make sure that it compiles 19 | #[allow(unused_imports, unused_variables, dead_code)] 20 | #[path = "null/mod.rs"] 21 | mod null; 22 | -------------------------------------------------------------------------------- /nativeshell/src/shell/handle.rs: -------------------------------------------------------------------------------- 1 | // Opaque handle for keeping a resource alive while handle exists 2 | pub struct Handle { 3 | on_cancel: Option>, 4 | } 5 | 6 | impl Handle { 7 | pub fn new(on_cancel: F) -> Self 8 | where 9 | F: FnOnce() + 'static, 10 | { 11 | Self { 12 | on_cancel: Some(Box::new(on_cancel)), 13 | } 14 | } 15 | 16 | pub fn cancel(&mut self) { 17 | if let Some(on_cancel) = self.on_cancel.take() { 18 | on_cancel(); 19 | } 20 | } 21 | 22 | pub fn detach(&mut self) { 23 | self.on_cancel.take(); 24 | } 25 | } 26 | 27 | impl Drop for Handle { 28 | fn drop(&mut self) { 29 | self.cancel(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/null/engine.rs: -------------------------------------------------------------------------------- 1 | use super::{binary_messenger::PlatformBinaryMessenger, error::PlatformResult}; 2 | 3 | pub type PlatformEngineType = isize; 4 | 5 | pub struct PlatformEngine { 6 | pub(crate) handle: PlatformEngineType, 7 | } 8 | 9 | pub type PlatformPlugin = isize; 10 | 11 | impl PlatformEngine { 12 | pub fn new(_plugins: &[PlatformPlugin]) -> Self { 13 | PlatformEngine { handle: 0 } 14 | } 15 | 16 | pub fn new_binary_messenger(&self) -> PlatformBinaryMessenger { 17 | PlatformBinaryMessenger {} 18 | } 19 | 20 | pub fn launch(&mut self) -> PlatformResult<()> { 21 | Err(super::error::PlatformError::NotImplemented) 22 | } 23 | 24 | pub fn shut_down(&mut self) -> PlatformResult<()> { 25 | Err(super::error::PlatformError::NotImplemented) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /nativeshell_build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nativeshell_build" 3 | version = "0.1.10" 4 | authors = ["Matej Knopp "] 5 | edition = "2018" 6 | description = "Integrate Flutter build process with Cargo" 7 | license = "MIT OR Apache-2.0" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | cargo-emit = "0.2.1" 13 | serde = { version = "1.0.119", features = ["derive"] } 14 | serde_json = "1.0" 15 | pathdiff = "0.2.0" 16 | path-slash = "0.1.4" 17 | dunce = "1.0.1" 18 | copy_dir = "0.1.2" 19 | yaml-rust = "0.4" 20 | 21 | [target.'cfg(target_os = "macos")'.dependencies] 22 | tar = "0.4" 23 | 24 | [target.'cfg(target_os = "windows")'.dependencies] 25 | cmake = "0.1" 26 | 27 | [target.'cfg(target_os = "linux")'.dependencies] 28 | cmake = "0.1" 29 | convert_case = "0.4.0" 30 | -------------------------------------------------------------------------------- /nativeshell/build.rs: -------------------------------------------------------------------------------- 1 | use nativeshell_build::Flutter; 2 | 3 | #[path = "keyboard_map/gen_keyboard_map.rs"] 4 | mod gen_keyboard_map; 5 | 6 | fn main() { 7 | #[cfg(target_os = "macos")] 8 | { 9 | let files = ["src/shell/platform/macos/window_buttons.m"]; 10 | let mut build = cc::Build::new(); 11 | for file in files.iter() { 12 | build.file(file); 13 | cargo_emit::rerun_if_changed!(file); 14 | } 15 | build.flag("-fobjc-arc"); 16 | build.compile("macos_extra"); 17 | } 18 | 19 | cargo_emit::rerun_if_env_changed!("FLUTTER_PROFILE"); 20 | if Flutter::build_mode() == "profile" { 21 | cargo_emit::rustc_cfg!("flutter_profile"); 22 | } 23 | 24 | let target_system = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); 25 | gen_keyboard_map::generate_keyboard_map(&target_system).unwrap(); 26 | } 27 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/null/binary_messenger.rs: -------------------------------------------------------------------------------- 1 | use crate::shell::BinaryMessengerReply; 2 | 3 | use super::error::{PlatformError, PlatformResult}; 4 | 5 | pub struct PlatformBinaryMessenger {} 6 | 7 | #[allow(unused_variables)] 8 | impl PlatformBinaryMessenger { 9 | pub fn register_channel_handler(&self, channel: &str, callback: F) 10 | where 11 | F: Fn(&[u8], BinaryMessengerReply) + 'static, 12 | { 13 | } 14 | 15 | pub fn unregister_channel_handler(&self, channel: &str) {} 16 | 17 | pub fn send_message(&self, channel: &str, message: &[u8], reply: F) -> PlatformResult<()> 18 | where 19 | F: FnOnce(&[u8]) + 'static, 20 | { 21 | Err(PlatformError::NotImplemented) 22 | } 23 | 24 | pub fn post_message(&self, channel: &str, message: &[u8]) -> PlatformResult<()> { 25 | Err(PlatformError::NotImplemented) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /nativeshell/src/util/log.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Display, panic::Location}; 2 | 3 | use log::{Level, Record}; 4 | 5 | pub trait OkLog { 6 | fn ok_log(self) -> Option; 7 | } 8 | 9 | impl OkLog for std::result::Result 10 | where 11 | E: Display, 12 | { 13 | #[track_caller] 14 | fn ok_log(self) -> Option { 15 | match self { 16 | Ok(value) => Some(value), 17 | Err(err) => { 18 | let location = Location::caller(); 19 | log::logger().log( 20 | &Record::builder() 21 | .args(format_args!("Unexpected error {} at {}", err, location)) 22 | .file(Some(location.file())) 23 | .line(Some(location.line())) 24 | .level(Level::Error) 25 | .build(), 26 | ); 27 | None 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /nativeshell_build/src/res/linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(NativeShellFlutterPlugins) 4 | 5 | # Compilation settings that should be applied to most targets. 6 | function(APPLY_STANDARD_SETTINGS TARGET) 7 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 8 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 9 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 10 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 11 | endfunction() 12 | 13 | # === Flutter Library === 14 | # System-level dependencies. 15 | find_package(PkgConfig REQUIRED) 16 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 17 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 18 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 19 | 20 | add_library(flutter INTERFACE) 21 | target_include_directories(flutter INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ) 22 | 23 | # Subdirectories 24 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/src/mutex.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | class Mutex { 4 | // Serialize execution of citical sections; For uncontended mutex the execution 5 | // is guaranteed to begin immediately (in this runloop turn) 6 | Future protect(Future Function() criticalSection) async { 7 | while (_locked) { 8 | await _waitUntilUnlocked(); 9 | } 10 | assert(!_locked); 11 | _locked = true; 12 | T res; 13 | try { 14 | _locked = true; 15 | res = await criticalSection(); 16 | } finally { 17 | _locked = false; 18 | _postUnlocked(); 19 | } 20 | return res; 21 | } 22 | 23 | Future _waitUntilUnlocked() { 24 | final c = Completer(); 25 | _after.add(c); 26 | return c.future; 27 | } 28 | 29 | void _postUnlocked() { 30 | if (_after.isNotEmpty) { 31 | final next = _after.removeAt(0); 32 | next.complete(); 33 | } 34 | } 35 | 36 | bool _locked = false; 37 | final _after = []; 38 | } 39 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/linux/init.rs: -------------------------------------------------------------------------------- 1 | use std::{ptr, rc::Weak}; 2 | 3 | use gdk::Event; 4 | use glib::ObjectExt; 5 | 6 | use crate::shell::platform::window::PlatformWindow; 7 | 8 | use super::error::{PlatformError, PlatformResult}; 9 | 10 | pub fn init_platform() -> PlatformResult<()> { 11 | gtk::init().map_err(|e| PlatformError::GLibError { 12 | message: e.message.into(), 13 | })?; 14 | 15 | Event::set_handler(Some(|e: &mut Event| { 16 | let win = e.window().map(|w| w.toplevel()); 17 | 18 | if let Some(win) = win { 19 | let platform_window: Option>> = 20 | unsafe { win.data("nativeshell_platform_window") }; 21 | 22 | if let Some(platform_window) = 23 | platform_window.and_then(|w| unsafe { w.as_ref() }.upgrade()) 24 | { 25 | platform_window.on_event(e); 26 | } 27 | } 28 | 29 | gtk::main_do_event(e); 30 | })); 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /nativeshell/src/util/errno.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::c_int; 2 | 3 | extern "C" { 4 | #[cfg(not(target_os = "dragonfly"))] 5 | #[cfg_attr( 6 | any(target_os = "macos", target_os = "ios", target_os = "freebsd"), 7 | link_name = "__error" 8 | )] 9 | #[cfg_attr( 10 | any( 11 | target_os = "openbsd", 12 | target_os = "netbsd", 13 | target_os = "bitrig", 14 | target_os = "android" 15 | ), 16 | link_name = "__errno" 17 | )] 18 | #[cfg_attr( 19 | any(target_os = "solaris", target_os = "illumos"), 20 | link_name = "___errno" 21 | )] 22 | #[cfg_attr(target_os = "linux", link_name = "__errno_location")] 23 | #[cfg_attr(target_os = "windows", link_name = "_errno")] 24 | fn errno_location() -> *mut c_int; 25 | } 26 | 27 | pub fn errno() -> c_int { 28 | unsafe { *errno_location() } 29 | } 30 | 31 | pub fn set_errno(errno: c_int) { 32 | unsafe { 33 | *errno_location() = errno; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/null/run_loop.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | pub type HandleType = usize; 4 | pub const INVALID_HANDLE: HandleType = 0; 5 | 6 | pub struct PlatformRunLoop {} 7 | 8 | #[allow(unused_variables)] 9 | impl PlatformRunLoop { 10 | pub fn new() -> Self { 11 | Self {} 12 | } 13 | 14 | pub fn unschedule(&self, handle: HandleType) {} 15 | 16 | #[must_use] 17 | pub fn schedule(&self, in_time: Duration, callback: F) -> HandleType 18 | where 19 | F: FnOnce() + 'static, 20 | { 21 | INVALID_HANDLE 22 | } 23 | 24 | pub fn run(&self) {} 25 | 26 | pub fn stop(&self) {} 27 | 28 | pub fn new_sender(&self) -> PlatformRunLoopSender { 29 | PlatformRunLoopSender {} 30 | } 31 | } 32 | 33 | #[derive(Clone)] 34 | pub struct PlatformRunLoopSender {} 35 | 36 | #[allow(unused_variables)] 37 | impl PlatformRunLoopSender { 38 | pub fn send(&self, callback: F) 39 | where 40 | F: FnOnce() + 'static + Send, 41 | { 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/null/hot_key.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Weak}; 2 | 3 | use crate::shell::{ 4 | api_model::Accelerator, Context, EngineHandle, HotKeyHandle, HotKeyManagerDelegate, 5 | }; 6 | 7 | use super::error::PlatformResult; 8 | 9 | pub(crate) struct PlatformHotKeyManager {} 10 | 11 | impl PlatformHotKeyManager { 12 | pub fn new(context: Context, delegate: Weak>) -> Self { 13 | Self {} 14 | } 15 | 16 | pub fn assign_weak_self(&self, weak: Weak) {} 17 | 18 | pub fn create_hot_key( 19 | &self, 20 | accelerator: Accelerator, 21 | virtual_key: i64, 22 | handle: HotKeyHandle, 23 | engine: EngineHandle, 24 | ) -> PlatformResult<()> { 25 | Ok(()) 26 | } 27 | 28 | pub fn destroy_hot_key(&self, handle: HotKeyHandle) -> PlatformResult<()> { 29 | Ok(()) 30 | } 31 | 32 | pub fn engine_destroyed(&self, engine: EngineHandle) -> PlatformResult<()> { 33 | Ok(()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /nativeshell/src/shell/bundle.rs: -------------------------------------------------------------------------------- 1 | // Only available on MacOS, on other platforms it's a no-op. 2 | // 3 | // If running unbundled and project was compiled with MacOSBundle, it will 4 | // re-run current process from within the bundle. 5 | // 6 | // This should be first thing called after application startup since it 7 | // essentially restarts the application. 8 | // 9 | // Generally running unbundled macOS GUI application causes various issues 10 | // (i.e. focus, unresponsive menu-bar, etc). It also means there is no access 11 | // to bundle resources and Info.plist contents. 12 | // 13 | // Calling MacOSBundle::build(options) in build.rs and exec_bundle() at 14 | // application startup allows you to run application as bundled using cargo run. 15 | // 16 | // Note: When debugging LLDB will pause on exec, to disable this you can add 17 | // "settings set target.process.stop-on-exec false" to LLDB configuration 18 | pub fn exec_bundle() { 19 | #[cfg(target_os = "macos")] 20 | { 21 | use super::platform::bundle::macos_exec_bundle; 22 | macos_exec_bundle(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/src/event.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | typedef Listener = void Function(T t); 4 | 5 | class Event { 6 | void addListener(Listener listener) { 7 | _listeners.add(listener); 8 | } 9 | 10 | void removeListener(Listener listener) { 11 | _listeners.remove(listener); 12 | } 13 | 14 | void fire(T arg) { 15 | final copy = List>.from(_listeners); 16 | for (final l in copy) { 17 | if (_listeners.contains(l)) { 18 | l(arg); 19 | } 20 | } 21 | } 22 | 23 | final _listeners = List>.empty(growable: true); 24 | } 25 | 26 | class VoidEvent { 27 | void addListener(VoidCallback listener) { 28 | _listeners.add(listener); 29 | } 30 | 31 | void removeListener(VoidCallback listener) { 32 | _listeners.remove(listener); 33 | } 34 | 35 | void fire() { 36 | final copy = List.from(_listeners); 37 | for (final l in copy) { 38 | if (_listeners.contains(l)) { 39 | l(); 40 | } 41 | } 42 | } 43 | 44 | final _listeners = List.empty(growable: true); 45 | } 46 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/linux/hot_key.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Weak}; 2 | 3 | use crate::shell::{ 4 | api_model::Accelerator, Context, EngineHandle, HotKeyHandle, HotKeyManagerDelegate, 5 | }; 6 | 7 | use super::error::{PlatformError, PlatformResult}; 8 | 9 | pub(crate) struct PlatformHotKeyManager {} 10 | 11 | impl PlatformHotKeyManager { 12 | pub fn new(_context: Context, _delegate: Weak>) -> Self { 13 | Self {} 14 | } 15 | 16 | pub fn assign_weak_self(&self, _weak: Weak) {} 17 | 18 | pub fn create_hot_key( 19 | &self, 20 | _accelerator: Accelerator, 21 | _virtual_key: i64, 22 | _handle: HotKeyHandle, 23 | _engine: EngineHandle, 24 | ) -> PlatformResult<()> { 25 | Err(PlatformError::NotAvailable) 26 | } 27 | 28 | pub fn destroy_hot_key(&self, _handle: HotKeyHandle) -> PlatformResult<()> { 29 | Err(PlatformError::NotAvailable) 30 | } 31 | 32 | pub fn engine_destroyed(&self, _engine: EngineHandle) -> PlatformResult<()> { 33 | Ok(()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /nativeshell/src/shell/mod.rs: -------------------------------------------------------------------------------- 1 | mod api_constants; 2 | mod async_method_call_handler; 3 | mod binary_messenger; 4 | mod bundle; 5 | mod context; 6 | mod engine; 7 | mod engine_manager; 8 | mod event_channel; 9 | mod geometry; 10 | mod handle; 11 | mod hot_key_manager; 12 | mod keyboard_map_manager; 13 | mod macros; 14 | mod menu_manager; 15 | mod message_manager; 16 | mod method_call_handler; 17 | mod observatory; 18 | mod run_loop; 19 | mod window; 20 | mod window_manager; 21 | mod window_method_channel; 22 | 23 | pub use async_method_call_handler::*; 24 | pub use binary_messenger::*; 25 | pub use bundle::*; 26 | pub use context::*; 27 | pub use engine::*; 28 | pub use engine_manager::*; 29 | pub use event_channel::*; 30 | pub use geometry::*; 31 | pub use handle::*; 32 | pub use hot_key_manager::*; 33 | pub use keyboard_map_manager::*; 34 | pub use macros::*; 35 | pub use menu_manager::*; 36 | pub use message_manager::*; 37 | pub use method_call_handler::*; 38 | pub use observatory::*; 39 | pub use run_loop::*; 40 | pub use window::*; 41 | pub use window_manager::*; 42 | pub use window_method_channel::*; 43 | 44 | pub mod api_model; 45 | pub mod platform; 46 | -------------------------------------------------------------------------------- /nativeshell_build/src/resources.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use crate::{ 4 | util::{get_absolute_path, get_artifacts_dir, mkdir, symlink}, 5 | BuildResult, 6 | }; 7 | 8 | pub struct Resources { 9 | pub(super) resources_dir: PathBuf, 10 | } 11 | 12 | impl Resources { 13 | pub fn new>(folder_name: P) -> BuildResult { 14 | let dir = mkdir(get_artifacts_dir()?, Some(folder_name))?; 15 | Ok(Self { resources_dir: dir }) 16 | } 17 | 18 | pub fn mkdir>(&self, sub_path: P) -> BuildResult<()> { 19 | mkdir(&self.resources_dir, Some(sub_path))?; 20 | Ok(()) 21 | } 22 | 23 | pub fn link(&self, src: P, dst: Q) -> BuildResult<()> 24 | where 25 | P: AsRef, 26 | Q: AsRef, 27 | { 28 | let src = get_absolute_path(src); 29 | let dst = self.resources_dir.join(dst); 30 | 31 | let dst = if dst.exists() { 32 | dst.join(src.file_name().unwrap()) 33 | } else { 34 | dst 35 | }; 36 | 37 | symlink(src, dst)?; 38 | 39 | Ok(()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /nativeshell_build/src/res/macos/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | ${BUNDLE_IDENTIFIER} 7 | CFBundleDevelopmentRegion 8 | English 9 | CFBundleExecutable 10 | ${BUNDLE_EXECUTABLE} 11 | SUEnableAutomaticChecks 12 | 13 | LSUIElement 14 | 15 | CFBundleIconFile 16 | ${ICON_FILE} 17 | CFBundleName 18 | ${BUNDLE_NAME} 19 | NSPrincipalClass 20 | NSApplication 21 | LSMinimumSystemVersion 22 | ${MINIMUM_SYSTEM_VERSION} 23 | CFBundleVersion 24 | ${BUNDLE_VERSION} 25 | CFBundleShortVersionString 26 | ${BUNDLE_SHORT_VERSION_STRING} 27 | CFBundlePackageType 28 | APPL 29 | NSSupportsAutomaticGraphicsSwitching 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/linux/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | #[derive(Debug, Clone)] 4 | pub enum PlatformError { 5 | NotImplemented, 6 | NotAvailable, 7 | UnknownError, 8 | GLibError { message: String }, 9 | OtherError { error: String }, 10 | } 11 | 12 | pub type PlatformResult = Result; 13 | 14 | impl Display for PlatformError { 15 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 16 | match self { 17 | PlatformError::NotImplemented => { 18 | write!(f, "Not Implemented") 19 | } 20 | PlatformError::NotAvailable => { 21 | write!(f, "Not Available") 22 | } 23 | PlatformError::UnknownError => { 24 | write!(f, "Unknown Error") 25 | } 26 | PlatformError::GLibError { message } => { 27 | write!(f, "GLibError: {}", message) 28 | } 29 | PlatformError::OtherError { error } => { 30 | write!(f, "{}", error) 31 | } 32 | } 33 | } 34 | } 35 | 36 | impl std::error::Error for PlatformError {} 37 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/null/menu.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | rc::{Rc, Weak}, 4 | }; 5 | 6 | use super::error::{PlatformError, PlatformResult}; 7 | use crate::shell::{api_model::Menu, Context, MenuDelegate, MenuHandle, MenuManager}; 8 | 9 | pub struct PlatformMenu {} 10 | 11 | #[allow(unused_variables)] 12 | impl PlatformMenu { 13 | pub fn new( 14 | context: Context, 15 | handle: MenuHandle, 16 | delegate: Weak>, 17 | ) -> Self { 18 | Self {} 19 | } 20 | 21 | pub fn assign_weak_self(&self, weak: Weak) {} 22 | 23 | pub fn update_from_menu(&self, menu: Menu, manager: &MenuManager) -> PlatformResult<()> { 24 | Err(PlatformError::NotImplemented) 25 | } 26 | } 27 | 28 | pub struct PlatformMenuManager {} 29 | 30 | impl PlatformMenuManager { 31 | pub fn new(context: Context) -> Self { 32 | Self {} 33 | } 34 | 35 | pub(crate) fn assign_weak_self(&self, _weak_self: Weak) {} 36 | 37 | pub fn set_app_menu(&self, menu: Option>) -> PlatformResult<()> { 38 | Err(PlatformError::NotImplemented) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/macos/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | #[derive(Debug, Clone)] 4 | pub enum PlatformError { 5 | UnknownError, 6 | LaunchEngineFailure, 7 | SendMessageFailure { channel: String }, 8 | NotAvailable, 9 | NoEventFound, 10 | } 11 | 12 | pub type PlatformResult = Result; 13 | 14 | impl Display for PlatformError { 15 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 16 | match self { 17 | PlatformError::UnknownError => { 18 | write!(f, "Unknown Error") 19 | } 20 | PlatformError::SendMessageFailure { channel } => { 21 | write!(f, "Failed to send message on channel {}", channel) 22 | } 23 | PlatformError::LaunchEngineFailure => { 24 | write!(f, "Failed to launch Flutter engine") 25 | } 26 | PlatformError::NoEventFound => { 27 | write!( 28 | f, 29 | "Action requires prior mouse event and the event was not found" 30 | ) 31 | } 32 | PlatformError::NotAvailable => { 33 | write!(f, "Feature is not available") 34 | } 35 | } 36 | } 37 | } 38 | 39 | impl std::error::Error for PlatformError {} 40 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/win32/init.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::null_mut; 2 | 3 | use windows::Win32::System::{ 4 | Com::{CoInitializeEx, COINIT_APARTMENTTHREADED}, 5 | LibraryLoader::LoadLibraryW, 6 | Ole::OleInitialize, 7 | }; 8 | 9 | use super::{ 10 | dpi::become_dpi_aware, 11 | dxgi_hook::init_dxgi_hook, 12 | error::{PlatformError, PlatformResult}, 13 | util::direct_composition_supported, 14 | }; 15 | 16 | pub fn init_platform() -> PlatformResult<()> { 17 | unsafe { 18 | // Angle will try opening these with GetModuleHandleEx, which means they need to be 19 | // loaded first; Otherwise it falls back to d3dcompiler_47, which is not present on 20 | // some Windows 7 installations. 21 | #[allow(clippy::collapsible_if)] 22 | if LoadLibraryW("d3dcompiler_47.dll").0 == 0 { 23 | if LoadLibraryW("d3dcompiler_46.dll").0 == 0 { 24 | LoadLibraryW("d3dcompiler_43.dll"); 25 | } 26 | } 27 | 28 | CoInitializeEx(null_mut(), COINIT_APARTMENTTHREADED).map_err(PlatformError::from)?; 29 | OleInitialize(null_mut()).map_err(PlatformError::from)?; 30 | 31 | // Needed for direct composition check 32 | LoadLibraryW("dcomp.dll"); 33 | } 34 | if direct_composition_supported() { 35 | init_dxgi_hook(); 36 | } 37 | become_dpi_aware(); 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /nativeshell/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use crate::{codec::value::ValueError, shell::platform::error::PlatformError}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub enum Error { 7 | InvalidContext, 8 | InvalidEngineHandle, 9 | Platform(PlatformError), 10 | Value(ValueError), 11 | InvalidMenuHandle, 12 | } 13 | 14 | impl Display for Error { 15 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 16 | match self { 17 | Error::InvalidContext => { 18 | write!(f, "Context was already destroyed") 19 | } 20 | Error::Platform(error) => Display::fmt(error, f), 21 | Error::InvalidEngineHandle => { 22 | write!(f, "Provided handle does not match any engine") 23 | } 24 | Error::Value(error) => Display::fmt(error, f), 25 | Error::InvalidMenuHandle => { 26 | write!(f, "Provided menu handle does not match any known menu") 27 | } 28 | } 29 | } 30 | } 31 | 32 | pub type Result = std::result::Result; 33 | 34 | impl std::error::Error for Error {} 35 | 36 | impl From for Error { 37 | fn from(src: PlatformError) -> Error { 38 | Error::Platform(src) 39 | } 40 | } 41 | 42 | impl From for Error { 43 | fn from(src: ValueError) -> Error { 44 | Error::Value(src) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /nativeshell/src/shell/engine.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | platform::engine::{PlatformEngine, PlatformEngineType, PlatformPlugin}, 3 | BinaryMessenger, EngineHandle, 4 | }; 5 | use crate::Result; 6 | 7 | pub struct FlutterEngine { 8 | pub(super) platform_engine: PlatformEngine, 9 | pub(super) parent_engine: Option, 10 | binary_messenger: Option, 11 | } 12 | 13 | impl FlutterEngine { 14 | pub fn new(plugins: &[PlatformPlugin], parent_engine: Option) -> Self { 15 | let platform_engine = PlatformEngine::new(plugins); 16 | 17 | let messenger = BinaryMessenger::new(platform_engine.new_binary_messenger()); 18 | FlutterEngine { 19 | platform_engine, 20 | parent_engine, 21 | binary_messenger: Some(messenger), 22 | } 23 | } 24 | 25 | pub fn binary_messenger(&self) -> &BinaryMessenger { 26 | self.binary_messenger.as_ref().unwrap() 27 | } 28 | 29 | pub fn platform_engine(&self) -> PlatformEngineType { 30 | #[allow(clippy::clone_on_copy)] 31 | self.platform_engine.handle.clone() 32 | } 33 | 34 | pub fn launch(&mut self) -> Result<()> { 35 | self.platform_engine.launch().map_err(|e| e.into()) 36 | } 37 | 38 | pub fn shut_down(&mut self) -> Result<()> { 39 | self.binary_messenger.take(); 40 | self.platform_engine.shut_down().map_err(|e| e.into()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /nativeshell_build/src/res/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(NativeShellFlutterPlugins) 4 | 5 | # Compilation settings that should be applied to most targets. 6 | function(APPLY_STANDARD_SETTINGS TARGET) 7 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 8 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 9 | target_compile_options(${TARGET} PRIVATE /EHsc) 10 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 11 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 12 | 13 | # Additional settings 14 | target_compile_options(${TARGET} PRIVATE "$<$:/MDd>") # needs to override cargo-rs /MD 15 | target_compile_definitions(${TARGET} PRIVATE "-D_UNICODE") 16 | endfunction() 17 | 18 | add_library(flutter_wrapper_plugin STATIC 19 | "cpp_client_wrapper/core_implementations.cc" 20 | "cpp_client_wrapper/standard_codec.cc" 21 | "cpp_client_wrapper/plugin_registrar.cc" 22 | ) 23 | apply_standard_settings(flutter_wrapper_plugin) 24 | 25 | target_include_directories(flutter_wrapper_plugin PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 26 | 27 | add_library(flutter INTERFACE) 28 | target_include_directories(flutter INTERFACE 29 | ${CMAKE_CURRENT_SOURCE_DIR} 30 | "${CMAKE_CURRENT_SOURCE_DIR}/cpp_client_wrapper/include") 31 | target_link_libraries(flutter INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/flutter_windows.dll.lib") 32 | 33 | # Subdirectories 34 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/win32/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | #[derive(Debug, Clone)] 4 | pub enum PlatformError { 5 | UnknownError, 6 | LaunchEngineFailure, 7 | SendMessageFailure { channel: String }, 8 | WindowsError(windows::core::Error), 9 | NotAvailable, 10 | OtherError { error: String }, 11 | } 12 | 13 | pub type PlatformResult = Result; 14 | 15 | impl Display for PlatformError { 16 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 17 | match self { 18 | PlatformError::UnknownError => { 19 | write!(f, "Unknown Error") 20 | } 21 | PlatformError::SendMessageFailure { channel } => { 22 | write!(f, "Failed to send message on channel {}", channel) 23 | } 24 | PlatformError::LaunchEngineFailure => { 25 | write!(f, "Failed to launch Flutter engine") 26 | } 27 | PlatformError::WindowsError(error) => error.fmt(f), 28 | PlatformError::NotAvailable => { 29 | write!(f, "Feature is not available") 30 | } 31 | PlatformError::OtherError { error } => { 32 | write!(f, "{}", error) 33 | } 34 | } 35 | } 36 | } 37 | 38 | impl std::error::Error for PlatformError {} 39 | 40 | impl From for PlatformError { 41 | fn from(src: windows::core::Error) -> PlatformError { 42 | PlatformError::WindowsError(src) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/linux/engine.rs: -------------------------------------------------------------------------------- 1 | use gtk::prelude::WidgetExt; 2 | 3 | use super::{ 4 | binary_messenger::PlatformBinaryMessenger, 5 | error::PlatformResult, 6 | flutter::{self, Engine, EngineExt, ViewExt}, 7 | }; 8 | 9 | pub type PlatformEngineType = Engine; 10 | 11 | pub struct PlatformEngine { 12 | pub(super) view: flutter::View, 13 | pub(crate) handle: PlatformEngineType, 14 | } 15 | 16 | pub struct PlatformPlugin { 17 | pub name: String, 18 | pub register_func: Option, 19 | } 20 | 21 | impl PlatformEngine { 22 | pub fn new(plugins: &[PlatformPlugin]) -> Self { 23 | let project = flutter::DartProject::new(); 24 | let view = flutter::View::new(&project); 25 | for plugin in plugins { 26 | let registrar = view.get_registrar_for_plugin(&plugin.name); 27 | if let Some(func) = plugin.register_func { 28 | unsafe { 29 | func(registrar); 30 | } 31 | } 32 | } 33 | PlatformEngine { 34 | view: view.clone(), 35 | handle: view.get_engine(), 36 | } 37 | } 38 | 39 | pub fn new_binary_messenger(&self) -> PlatformBinaryMessenger { 40 | PlatformBinaryMessenger::new(self.view.get_engine().get_binary_messenger()) 41 | } 42 | 43 | pub fn launch(&mut self) -> PlatformResult<()> { 44 | // This assumes the view has already been added to GtkWindow 45 | self.view.realize(); 46 | Ok(()) 47 | } 48 | 49 | pub fn shut_down(&mut self) -> PlatformResult<()> { 50 | Ok(()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /nativeshell/src/util/future.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc, task::Poll}; 2 | 3 | use futures::Future; 4 | 5 | // 6 | // Single threaded completable future 7 | // 8 | 9 | struct State { 10 | waker: Option, 11 | data: Option, 12 | } 13 | 14 | pub struct FutureCompleter { 15 | state: Rc>>, 16 | } 17 | 18 | impl FutureCompleter { 19 | pub fn new() -> (CompletableFuture, FutureCompleter) { 20 | let state = Rc::new(RefCell::new(State { 21 | waker: None, 22 | data: None, 23 | })); 24 | ( 25 | CompletableFuture { 26 | state: state.clone(), 27 | }, 28 | FutureCompleter { state }, 29 | ) 30 | } 31 | 32 | pub fn complete(self, data: T) { 33 | let waker = { 34 | let mut state = self.state.borrow_mut(); 35 | state.data.replace(data); 36 | state.waker.take() 37 | }; 38 | if let Some(waker) = waker { 39 | waker.wake(); 40 | } 41 | } 42 | } 43 | 44 | pub struct CompletableFuture { 45 | state: Rc>>, 46 | } 47 | 48 | impl Future for CompletableFuture { 49 | type Output = T; 50 | 51 | fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { 52 | let mut state = self.state.borrow_mut(); 53 | let data = state.data.take(); 54 | match data { 55 | Some(data) => Poll::Ready(data), 56 | None => { 57 | state.waker.get_or_insert_with(|| cx.waker().clone()); 58 | Poll::Pending 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/linux/binary_messenger.rs: -------------------------------------------------------------------------------- 1 | use crate::shell::BinaryMessengerReply; 2 | 3 | use super::{ 4 | error::PlatformResult, 5 | flutter::{self, BinaryMessengerExt}, 6 | }; 7 | 8 | pub struct PlatformBinaryMessenger { 9 | messenger: flutter::BinaryMessenger, 10 | } 11 | 12 | #[allow(unused_variables)] 13 | impl PlatformBinaryMessenger { 14 | pub fn new(messenger: flutter::BinaryMessenger) -> Self { 15 | Self { messenger } 16 | } 17 | 18 | pub fn register_channel_handler(&self, channel: &str, callback: F) 19 | where 20 | F: Fn(&[u8], BinaryMessengerReply) + 'static, 21 | { 22 | self.messenger.set_message_handler_on_channel( 23 | channel, 24 | move |bytes, channel, messenger, response| { 25 | let reply = BinaryMessengerReply::new(move |data| { 26 | messenger.send_response(response, data.into()); 27 | }); 28 | callback(&bytes, reply); 29 | }, 30 | ); 31 | } 32 | 33 | pub fn unregister_channel_handler(&self, channel: &str) { 34 | self.messenger.remove_message_handler_on_channel(channel); 35 | } 36 | 37 | pub fn send_message(&self, channel: &str, message: &[u8], reply: F) -> PlatformResult<()> 38 | where 39 | F: FnOnce(&[u8]) + 'static, 40 | { 41 | self.messenger 42 | .send_message(channel, message.into(), move |data| { 43 | reply(&data); 44 | }); 45 | Ok(()) 46 | } 47 | 48 | pub fn post_message(&self, channel: &str, message: &[u8]) -> PlatformResult<()> { 49 | self.messenger.post_message(channel, message.into()); 50 | Ok(()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/check_and_lint.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: 5 | - main 6 | 7 | name: Check and Lint 8 | 9 | jobs: 10 | 11 | Flutter: 12 | runs-on: ubuntu-latest 13 | defaults: 14 | run: 15 | working-directory: ./nativeshell_dart 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: subosito/flutter-action@v1 19 | with: 20 | channel: 'master' 21 | - run: flutter pub get 22 | - run: flutter format --output=none --set-exit-if-changed . 23 | - run: flutter analyze 24 | # - run: flutter test 25 | 26 | Rustfmt: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v2 30 | - uses: actions-rs/toolchain@v1 31 | with: 32 | profile: minimal 33 | toolchain: stable 34 | override: true 35 | - run: rustup component add rustfmt 36 | - uses: actions-rs/cargo@v1 37 | with: 38 | command: fmt 39 | args: --all -- --check 40 | 41 | Rust: 42 | runs-on: ${{ matrix.os }} 43 | strategy: 44 | fail-fast: false 45 | matrix: 46 | os: 47 | - ubuntu-latest 48 | - macOS-latest 49 | - windows-latest 50 | steps: 51 | - name: Install GTK 52 | if: (matrix.os == 'ubuntu-latest') 53 | run: sudo apt-get update && sudo apt-get install libgtk-3-dev 54 | - uses: actions/checkout@v2 55 | - name: Install clippy 56 | run: rustup component add clippy 57 | - name: Run cargo clippy 58 | uses: actions-rs/cargo@v1 59 | with: 60 | command: clippy 61 | args: -- -D warnings 62 | - name: Run cargo test 63 | uses: actions-rs/cargo@v1 64 | with: 65 | command: test 66 | -------------------------------------------------------------------------------- /nativeshell_dart/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.1.10] - 2021-10-11 11 | 12 | - Breaking change: Renamed `location` field on `DragEvent` to `position`. 13 | - Added `DragEvent.globalPosition` 14 | - Added `DropMonitor` widget 15 | 16 | ## [0.1.9] - 2021-08-21 17 | 18 | - Breaking change: Added WindowLayoutProbe widget required for WindowSizingMode.atLeastIntrinsicSize and WindowSizingMode.sizeToContents 19 | - Added HotKey class for registering global hotkeys 20 | - Added KeyboardMap class (mapping between physical and logical keys + keyboard layout change notification) 21 | - Bugfixes 22 | 23 | ## [0.1.8] - 2021-06-16 24 | 25 | - Performance improvements when opening new windows. 26 | - Window sizing fixes on Linux. 27 | 28 | ## [0.1.7] - 2021-06-16 29 | 30 | - Added Menu.onOpen callback. 31 | 32 | ## [0.1.6] - 2021-06-13 33 | 34 | - Made `WindowState.windowSizingMode` abstract so that it must be specified explicitely. 35 | - All `DragDataKey` constructuctor arguments are now named. 36 | - `DragDataDecode` returns nullable result. 37 | - Added `IntrinsicSizedBox` widget. 38 | 39 | ## [0.1.5] - 2021-06-07 40 | 41 | - Fix window sizing regression. 42 | 43 | ## [0.1.4] - 2021-06-07 44 | 45 | - Replaced `WindowState.autoSizeWindow` with `WindowState.windowSizingMode`. 46 | - Removed `WindowState.requestUpdateConstraints()`, as it is no longer necessary to call it with `WindowSizingMode.atLeastIntrinsicSize` (default value). 47 | 48 | ## [0.1.3] - 2021-06-03 49 | 50 | - Rename `WindowBuilder` to `WindowState`. 51 | 52 | ## [0.1.0] - 2021-05-29 53 | 54 | - Initial release 55 | 56 | -------------------------------------------------------------------------------- /nativeshell_dart/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: nativeshell 2 | description: NativeShell Dart API. Used to interact with NativeShell platform code. 3 | version: 0.1.10 4 | homepage: https://nativeshell.dev 5 | repository: https://github.com/nativeshell/nativeshell 6 | 7 | environment: 8 | sdk: ">=2.13.0 <3.0.0" 9 | flutter: ">=2.2.1" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | pedantic: ^1.9.2 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | # For information on the generic Dart part of this file, see the 21 | # following page: https://dart.dev/tools/pub/pubspec 22 | 23 | # The following section is specific to Flutter. 24 | flutter: 25 | 26 | # To add assets to your package, add an assets section, like this: 27 | # assets: 28 | # - images/a_dot_burr.jpeg 29 | # - images/a_dot_ham.jpeg 30 | # 31 | # For details regarding assets in packages, see 32 | # https://flutter.dev/assets-and-images/#from-packages 33 | # 34 | # An image asset can refer to one or more resolution-specific "variants", see 35 | # https://flutter.dev/assets-and-images/#resolution-aware. 36 | 37 | # To add custom fonts to your package, add a fonts section here, 38 | # in this "flutter" section. Each entry in this list should have a 39 | # "family" key with the font family name, and a "fonts" key with a 40 | # list giving the asset and other descriptors for the font. For 41 | # example: 42 | # fonts: 43 | # - family: Schyler 44 | # fonts: 45 | # - asset: fonts/Schyler-Regular.ttf 46 | # - asset: fonts/Schyler-Italic.ttf 47 | # style: italic 48 | # - family: Trajan Pro 49 | # fonts: 50 | # - asset: fonts/TrajanPro.ttf 51 | # - asset: fonts/TrajanPro_Bold.ttf 52 | # weight: 700 53 | # 54 | # For details regarding fonts in packages, see 55 | # https://flutter.dev/custom-fonts/#from-packages 56 | -------------------------------------------------------------------------------- /nativeshell/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Matej Knopp 2 | 3 | ================================================================================ 4 | 5 | MIT LICENSE 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | of the Software, and to permit persons to whom the Software is furnished to do 12 | so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 20 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | ================================================================================ 25 | 26 | APACHE LICENSE, VERSION 2.0 27 | 28 | Licensed under the Apache License, Version 2.0 (the "License"); 29 | you may not use this file except in compliance with the License. 30 | You may obtain a copy of the License at 31 | 32 | http://www.apache.org/licenses/LICENSE-2.0 33 | 34 | Unless required by applicable law or agreed to in writing, software 35 | distributed under the License is distributed on an "AS IS" BASIS, 36 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 37 | See the License for the specific language governing permissions and 38 | limitations under the License. 39 | 40 | -------------------------------------------------------------------------------- /nativeshell_dart/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Matej Knopp 2 | 3 | ================================================================================ 4 | 5 | MIT LICENSE 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | of the Software, and to permit persons to whom the Software is furnished to do 12 | so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 20 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | ================================================================================ 25 | 26 | APACHE LICENSE, VERSION 2.0 27 | 28 | Licensed under the Apache License, Version 2.0 (the "License"); 29 | you may not use this file except in compliance with the License. 30 | You may obtain a copy of the License at 31 | 32 | http://www.apache.org/licenses/LICENSE-2.0 33 | 34 | Unless required by applicable law or agreed to in writing, software 35 | distributed under the License is distributed on an "AS IS" BASIS, 36 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 37 | See the License for the specific language governing permissions and 38 | limitations under the License. 39 | 40 | -------------------------------------------------------------------------------- /nativeshell_build/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Matej Knopp 2 | 3 | ================================================================================ 4 | 5 | MIT LICENSE 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | of the Software, and to permit persons to whom the Software is furnished to do 12 | so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 20 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | ================================================================================ 25 | 26 | APACHE LICENSE, VERSION 2.0 27 | 28 | Licensed under the Apache License, Version 2.0 (the "License"); 29 | you may not use this file except in compliance with the License. 30 | You may obtain a copy of the License at 31 | 32 | http://www.apache.org/licenses/LICENSE-2.0 33 | 34 | Unless required by applicable law or agreed to in writing, software 35 | distributed under the License is distributed on an "AS IS" BASIS, 36 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 37 | See the License for the specific language governing permissions and 38 | limitations under the License. 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NativeShell (Experimental embedder for Flutter) 2 | 3 | ![](https://nativeshell.dev/screenshot-dev.png "Screenshot") 4 | 5 | ## Sponsors 6 | 7 | 8 | 9 | ## Features 10 | 11 | - Leverages existing Flutter desktop embedder on each platform 12 | - Unlike Flutter desktop embedders, NativeShell provides consistent platform agnostic API 13 | - Multi-window support 14 | - Window management 15 | - Adjusting window styles and geometry 16 | - Modal dialogs 17 | - Windows can be set to track content size and resize automatically when content changes 18 | - Platform menus (popup menu, menu bar) 19 | - Drag and Drop 20 | - Written in Rust, Flutter build transparently integrated with cargo 21 | 22 | ## Status 23 | 24 | - This is project in a very experimental stage 25 | 26 | ## Getting started 27 | 28 | Prerequisites: 29 | 30 | 1. [Install Rust](https://www.rust-lang.org/tools/install) 31 | 2. [Install Flutter](https://flutter.dev/docs/get-started/install) 32 | 3. [Enable Flutter desktop support](https://flutter.dev/desktop#set-up) 33 | 4. Switch to Flutter Master (`flutter channel master; flutter upgrade`) 34 | 35 | Clone and run examples: 36 | 37 | ```bash 38 | git clone https://github.com/nativeshell/examples.git 39 | cd examples 40 | cargo run 41 | ``` 42 | 43 | For Apple Silicon Macs, you might need to run the example using the flag to force x86_64 architecture: 44 | 45 | ```bash 46 | rustup target add x86_64-apple-darwin 47 | cargo run --target=x86_64-apple-darwin 48 | ``` 49 | 50 | Alternatively you can use environment variables: 51 | ```bash 52 | # Recommended if using rust-analyzer to minimize redundant rebuilds 53 | export CARGO_TARGET_DIR=target/x86_64 54 | export CARGO_BUILD_TARGET=x86_64-apple-darwin 55 | cargo run 56 | ``` 57 | 58 | For more information read the [introductory post](https://matejknopp.com/post/introducing-nativeshell/) or go to [nativeshell.dev](https://nativeshell.dev). 59 | 60 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/src/accelerators.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/services.dart'; 3 | import 'accelerator.dart'; 4 | 5 | const ctrl = Accelerator(control: true); 6 | const cmd = Accelerator(meta: true); 7 | const alt = Accelerator(alt: true); 8 | final cmdOrCtrl = _cmdOrCtrl(); 9 | final shift = Accelerator(shift: true); 10 | final noModifier = Accelerator(); 11 | 12 | final f1 = _accelerator(LogicalKeyboardKey.f1); 13 | final f2 = _accelerator(LogicalKeyboardKey.f2); 14 | final f3 = _accelerator(LogicalKeyboardKey.f3); 15 | final f4 = _accelerator(LogicalKeyboardKey.f4); 16 | final f5 = _accelerator(LogicalKeyboardKey.f5); 17 | final f6 = _accelerator(LogicalKeyboardKey.f6); 18 | final f7 = _accelerator(LogicalKeyboardKey.f7); 19 | final f8 = _accelerator(LogicalKeyboardKey.f8); 20 | final f9 = _accelerator(LogicalKeyboardKey.f9); 21 | final f10 = _accelerator(LogicalKeyboardKey.f10); 22 | final f11 = _accelerator(LogicalKeyboardKey.f11); 23 | final f12 = _accelerator(LogicalKeyboardKey.f12); 24 | final home = _accelerator(LogicalKeyboardKey.home); 25 | final end = _accelerator(LogicalKeyboardKey.end); 26 | final insert = _accelerator(LogicalKeyboardKey.insert); 27 | final delete = _accelerator(LogicalKeyboardKey.delete); 28 | final backspace = _accelerator(LogicalKeyboardKey.backspace); 29 | final pageUp = _accelerator(LogicalKeyboardKey.pageUp); 30 | final pageDown = _accelerator(LogicalKeyboardKey.pageDown); 31 | final space = _accelerator(LogicalKeyboardKey.space); 32 | final tab = _accelerator(LogicalKeyboardKey.tab); 33 | final enter = _accelerator(LogicalKeyboardKey.enter); 34 | final arrowUp = _accelerator(LogicalKeyboardKey.arrowUp); 35 | final arrowDown = _accelerator(LogicalKeyboardKey.arrowDown); 36 | final arrowLeft = _accelerator(LogicalKeyboardKey.arrowLeft); 37 | final arrowRight = _accelerator(LogicalKeyboardKey.arrowRight); 38 | 39 | Accelerator _cmdOrCtrl() { 40 | return Accelerator(meta: Platform.isMacOS, control: !Platform.isMacOS); 41 | } 42 | 43 | Accelerator _accelerator(LogicalKeyboardKey key) => Accelerator(key: key); 44 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/src/key_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | typedef KeyInterceptorHandler = bool Function(RawKeyEvent event); 5 | 6 | enum InterceptorStage { 7 | pre, // interceptor executed before flutter keyboard handler (for all events) 8 | post, // interceptor executed after flutter keyboard handler (for unhandled events only) 9 | } 10 | 11 | class KeyInterceptor { 12 | KeyInterceptor._() { 13 | WidgetsFlutterBinding.ensureInitialized(); 14 | _previousHandler = 15 | ServicesBinding.instance!.keyEventManager.keyMessageHandler; 16 | ServicesBinding.instance!.keyEventManager.keyMessageHandler = _onMessage; 17 | } 18 | 19 | void registerHandler( 20 | KeyInterceptorHandler handler, { 21 | required InterceptorStage stage, 22 | }) { 23 | if (stage == InterceptorStage.pre) { 24 | _handlersPre.add(handler); 25 | } else { 26 | _handlersPost.add(handler); 27 | } 28 | } 29 | 30 | void unregisterHandler( 31 | KeyInterceptorHandler handler, { 32 | required InterceptorStage stage, 33 | }) { 34 | if (stage == InterceptorStage.pre) { 35 | _handlersPre.remove(handler); 36 | } else { 37 | _handlersPost.remove(handler); 38 | } 39 | } 40 | 41 | static final KeyInterceptor instance = KeyInterceptor._(); 42 | 43 | final _handlersPre = []; 44 | final _handlersPost = []; 45 | 46 | KeyMessageHandler? _previousHandler; 47 | 48 | bool _onMessage(KeyMessage message) { 49 | for (final handler in List.from(_handlersPre)) { 50 | if (handler(message.rawEvent)) { 51 | return true; 52 | } 53 | } 54 | if (_previousHandler != null && _previousHandler!(message)) { 55 | return true; 56 | } 57 | for (final handler in List.from(_handlersPost)) { 58 | if (handler(message.rawEvent)) { 59 | return true; 60 | } 61 | } 62 | return false; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /nativeshell/src/util/diff.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum DiffResult { 3 | Remove(T), 4 | Update(T, T), 5 | Keep(T, T), 6 | Insert(T), 7 | } 8 | 9 | pub fn update_diff<'a, T: PartialEq, F>( 10 | old: &'a [T], 11 | new: &'a [T], 12 | can_update: F, 13 | ) -> Vec> 14 | where 15 | F: Fn(&T, &T) -> bool, 16 | { 17 | let mut diff = diff::slice(old, new); 18 | let mut res = Vec::>::new(); 19 | let mut i = 0; 20 | // Convert sequences to 21 | // for items where can_update returns true. 22 | while i < diff.len() { 23 | let cur = &diff[i]; 24 | match cur { 25 | diff::Result::Left(remove) => { 26 | let mut next_add = i + 1; 27 | let mut next_add_value: Option<&T> = None; 28 | while next_add < diff.len() { 29 | match &diff[next_add] { 30 | diff::Result::Left(_) => { 31 | next_add += 1; 32 | } 33 | diff::Result::Both(_, _) => { 34 | next_add = diff.len(); 35 | } 36 | diff::Result::Right(add) => { 37 | next_add_value.replace(add); 38 | break; 39 | } 40 | } 41 | } 42 | if next_add < diff.len() && can_update(remove, next_add_value.as_ref().unwrap()) { 43 | res.push(DiffResult::Update(remove, next_add_value.as_ref().unwrap())); 44 | diff.remove(next_add); 45 | } else { 46 | res.push(DiffResult::Remove(remove)); 47 | } 48 | } 49 | diff::Result::Both(same1, same2) => res.push(DiffResult::Keep(same1, same2)), 50 | diff::Result::Right(added) => res.push(DiffResult::Insert(added)), 51 | } 52 | i += 1; 53 | } 54 | res 55 | } 56 | -------------------------------------------------------------------------------- /.github/workflows/build_test.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: 5 | - main 6 | 7 | name: Build 8 | 9 | jobs: 10 | Main: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | platform: 15 | - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } 16 | # PKG config cross-compiling needs additional work 17 | # - { target: aarch64-unknown-linux-gnu, os: ubuntu-latest, } 18 | - { target: x86_64-pc-windows-msvc, os: windows-latest, } 19 | - { target: x86_64-apple-darwin, os: macos-latest, } 20 | project: 21 | - project1 22 | - project2 23 | configuration: 24 | - debug 25 | - release 26 | include: 27 | - configuration: debug 28 | cargo_args: "" 29 | - configuration: release 30 | cargo_args: --release 31 | 32 | runs-on: ${{ matrix.platform.os }} 33 | steps: 34 | - name: Install GTK 35 | if: (matrix.platform.os == 'ubuntu-latest') 36 | run: sudo apt-get update && sudo apt-get install libgtk-3-dev 37 | - uses: actions/checkout@v2 38 | - uses: subosito/flutter-action@v1 39 | with: 40 | channel: 'master' 41 | - uses: actions-rs/toolchain@v1 42 | with: 43 | toolchain: stable 44 | - run: rustup target add ${{ matrix.platform.target }} 45 | - run: flutter config --enable-windows-desktop 46 | - run: flutter config --enable-linux-desktop 47 | - run: flutter config --enable-macos-desktop 48 | - run: flutter pub get 49 | working-directory: ./build_test/${{ matrix.project }} 50 | - run: cargo build --target=${{ matrix.platform.target }} ${{ matrix.cargo_args }} 51 | working-directory: ./build_test/${{ matrix.project }} 52 | - if: (matrix.configuration == 'release') 53 | run: cargo build --target=${{ matrix.platform.target }} ${{ matrix.cargo_args }} 54 | working-directory: ./build_test/${{ matrix.project }} 55 | env: 56 | FLUTTER_PROFILE: 1 57 | 58 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/src/keyboard_map_internal.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | import 'api_constants.dart'; 4 | import 'api_model_internal.dart' as model; 5 | import 'keyboard_map.dart'; 6 | 7 | final _keyboardMapChannel = MethodChannel(Channels.keyboardMapManager); 8 | 9 | class KeyboardMapManager { 10 | static final instance = KeyboardMapManager._(); 11 | 12 | Future _invoke(String method, dynamic arg) { 13 | return _keyboardMapChannel.invokeMethod(method, arg); 14 | } 15 | 16 | Future _onMethodCall(MethodCall call) async { 17 | if (call.method == Methods.keyboardMapOnChanged) { 18 | _update(model.KeyboardMap.deserialize(call.arguments)); 19 | KeyboardMap.onChange.fire(); 20 | } 21 | } 22 | 23 | KeyboardMapManager._() { 24 | _keyboardMapChannel.setMethodCallHandler(_onMethodCall); 25 | } 26 | 27 | Future init() async { 28 | final keyboard = await _invoke(Methods.keyboardMapGet, null); 29 | _update(model.KeyboardMap.deserialize(keyboard)); 30 | } 31 | 32 | void _update(model.KeyboardMap map) { 33 | final platformToKey = {}; 34 | final physicalToKey = {}; 35 | final logicalToKey = {}; 36 | 37 | for (final key in map.keys) { 38 | platformToKey[key.platform] = key; 39 | physicalToKey[key.physical] = key; 40 | if (key.logicalAltShift != null) { 41 | logicalToKey[key.logicalAltShift!] = key; 42 | } 43 | if (key.logicalAlt != null) { 44 | logicalToKey[key.logicalAlt!] = key; 45 | } 46 | if (key.logicalShift != null) { 47 | logicalToKey[key.logicalShift!] = key; 48 | } 49 | if (key.logicalMeta != null) { 50 | logicalToKey[key.logicalMeta!] = key; 51 | } 52 | if (key.logical != null) { 53 | logicalToKey[key.logical!] = key; 54 | } 55 | } 56 | 57 | _currentMap = KeyboardMap(platformToKey, physicalToKey, logicalToKey); 58 | } 59 | 60 | late KeyboardMap _currentMap; 61 | 62 | KeyboardMap get currentMap => _currentMap; 63 | } 64 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/win32/dpi.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Once; 2 | 3 | use windows::Win32::{ 4 | Foundation::BOOL, 5 | System::LibraryLoader::{FreeLibrary, GetProcAddress, LoadLibraryW}, 6 | UI::WindowsAndMessaging::SetProcessDPIAware, 7 | }; 8 | 9 | fn set_per_monitor_aware() -> bool { 10 | const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2: isize = -4; 11 | const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE: isize = -3; 12 | 13 | let mut res = false; 14 | unsafe { 15 | let module = LoadLibraryW("User32.dll"); 16 | if module.0 != 0 { 17 | let function = GetProcAddress(module, "SetProcessDpiAwarenessContext"); 18 | if let Some(set_awareness_context) = function { 19 | let function: extern "system" fn(isize) -> BOOL = 20 | std::mem::transmute(set_awareness_context); 21 | // Windows 10 Anniversary Update (1607) or later 22 | if !function(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2).as_bool() { 23 | function(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); 24 | } 25 | res = true; 26 | } 27 | FreeLibrary(module); 28 | } 29 | } 30 | res 31 | } 32 | 33 | fn set_per_monitor_dpi_aware_fallback() -> bool { 34 | const PROCESS_PER_MONITOR_DPI_AWARE: i32 = 2; 35 | 36 | let mut res = false; 37 | unsafe { 38 | let module = LoadLibraryW("Shcore.dll"); 39 | if module.0 != 0 { 40 | let function = GetProcAddress(module, "SetProcessDpiAwareness"); 41 | if let Some(set_awareness) = function { 42 | let function: extern "system" fn(i32) -> BOOL = std::mem::transmute(set_awareness); 43 | function(PROCESS_PER_MONITOR_DPI_AWARE); 44 | res = true; 45 | } 46 | FreeLibrary(module); 47 | } 48 | } 49 | res 50 | } 51 | 52 | pub fn become_dpi_aware() { 53 | static BECOME_AWARE: Once = Once::new(); 54 | BECOME_AWARE.call_once(|| { 55 | if !set_per_monitor_aware() && !set_per_monitor_dpi_aware_fallback() { 56 | unsafe { SetProcessDPIAware() }; 57 | } 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/src/intrinsic_sized_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/rendering.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | // Simple Widget that can provide intrinsic size; Useful when child 5 | // is unable to provide one 6 | class IntrinsicSizedBox extends SingleChildRenderObjectWidget { 7 | const IntrinsicSizedBox({ 8 | Key? key, 9 | this.intrinsicWidth, 10 | this.intrinsicHeight, 11 | Widget? child, 12 | }) : super(key: key, child: child); 13 | 14 | final double? intrinsicWidth; 15 | final double? intrinsicHeight; 16 | 17 | @override 18 | _RenderIntrinsicSizedBox createRenderObject(BuildContext context) { 19 | return _RenderIntrinsicSizedBox( 20 | intrinsicWidth: intrinsicWidth, 21 | intrinsicHeight: intrinsicHeight, 22 | ); 23 | } 24 | 25 | @override 26 | void updateRenderObject( 27 | BuildContext context, covariant RenderObject renderObject) { 28 | final object = renderObject as _RenderIntrinsicSizedBox; 29 | object.intrinsicWidth = intrinsicWidth; 30 | object.intrinsicHeight = intrinsicHeight; 31 | } 32 | } 33 | 34 | class _RenderIntrinsicSizedBox extends RenderProxyBox { 35 | _RenderIntrinsicSizedBox({ 36 | RenderBox? child, 37 | this.intrinsicWidth, 38 | this.intrinsicHeight, 39 | }) : super(child); 40 | 41 | double? intrinsicWidth; 42 | double? intrinsicHeight; 43 | 44 | @override 45 | double computeMaxIntrinsicWidth(double height) { 46 | return intrinsicWidth != null 47 | ? intrinsicWidth! 48 | : super.computeMaxIntrinsicWidth(height); 49 | } 50 | 51 | @override 52 | double computeMaxIntrinsicHeight(double width) { 53 | return intrinsicHeight != null 54 | ? intrinsicHeight! 55 | : super.computeMaxIntrinsicHeight(width); 56 | } 57 | 58 | @override 59 | double computeMinIntrinsicWidth(double height) { 60 | return intrinsicWidth != null 61 | ? intrinsicWidth! 62 | : super.computeMinIntrinsicWidth(height); 63 | } 64 | 65 | @override 66 | double computeMinIntrinsicHeight(double width) { 67 | return intrinsicHeight != null 68 | ? intrinsicHeight! 69 | : super.computeMinIntrinsicHeight(width); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /nativeshell/src/shell/binary_messenger.rs: -------------------------------------------------------------------------------- 1 | use super::platform::binary_messenger::PlatformBinaryMessenger; 2 | use crate::Result; 3 | 4 | pub struct BinaryMessengerReply { 5 | sent: bool, 6 | callback: Option>, 7 | } 8 | 9 | impl BinaryMessengerReply { 10 | pub fn new(callback: F) -> Self 11 | where 12 | F: FnOnce(&[u8]) + 'static, 13 | { 14 | BinaryMessengerReply { 15 | sent: false, 16 | callback: Some(Box::new(callback)), 17 | } 18 | } 19 | 20 | pub fn send(mut self, message: &[u8]) { 21 | self.sent = true; 22 | let callback = self.callback.take().unwrap(); 23 | callback(message); 24 | } 25 | } 26 | 27 | pub struct BinaryMessenger { 28 | messenger: PlatformBinaryMessenger, 29 | } 30 | 31 | impl BinaryMessenger { 32 | pub fn new(messenger_impl: PlatformBinaryMessenger) -> Self { 33 | BinaryMessenger { 34 | messenger: messenger_impl, 35 | } 36 | } 37 | 38 | pub fn register_channel_handler(&self, channel: &str, callback: F) 39 | where 40 | F: Fn(&[u8], BinaryMessengerReply) + 'static, 41 | { 42 | self.messenger.register_channel_handler(channel, callback); 43 | } 44 | 45 | pub fn unregister_channel_handler(&self, channel: &str) { 46 | self.messenger.unregister_channel_handler(channel); 47 | } 48 | 49 | pub fn send_message(&self, channel: &str, message: &[u8], reply_callback: F) -> Result<()> 50 | where 51 | F: FnOnce(&[u8]) + 'static, 52 | { 53 | self.messenger 54 | .send_message(channel, message, reply_callback) 55 | .map_err(|e| e.into()) 56 | } 57 | 58 | // like "send_message" but wihtout reply 59 | pub fn post_message(&self, channel: &str, message: &[u8]) -> Result<()> { 60 | self.messenger 61 | .post_message(channel, message) 62 | .map_err(|e| e.into()) 63 | } 64 | } 65 | 66 | impl Drop for BinaryMessengerReply { 67 | fn drop(&mut self) { 68 | if !self.sent { 69 | // Send empty reply so that the message doesn't leak 70 | let callback = self.callback.take().unwrap(); 71 | callback(&[]); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/macos/bundle.rs: -------------------------------------------------------------------------------- 1 | use cocoa::{base::id, foundation::NSBundle}; 2 | use fs::canonicalize; 3 | use log::warn; 4 | use objc::{msg_send, sel, sel_impl}; 5 | use process_path::get_executable_path; 6 | use std::{ 7 | fs, io, 8 | path::{Path, PathBuf}, 9 | }; 10 | 11 | fn is_running_bundled() -> bool { 12 | unsafe { 13 | let bundle: id = NSBundle::mainBundle(); 14 | let identifier: id = msg_send![bundle, bundleIdentifier]; 15 | !identifier.is_null() 16 | } 17 | } 18 | 19 | // Find bundle next to our executable that has a symlink to our executable 20 | fn find_bundle_executable>(executable_path: P) -> io::Result> { 21 | let parent = executable_path.as_ref().parent().unwrap(); 22 | for entry in fs::read_dir(parent)? { 23 | let entry = entry?; 24 | 25 | if entry.file_name().to_string_lossy().ends_with(".app") { 26 | let executables = entry.path().join("Contents/MacOS"); 27 | for entry in fs::read_dir(executables)? { 28 | let entry = entry?; 29 | let meta = fs::symlink_metadata(entry.path())?; 30 | if meta.file_type().is_symlink() { 31 | let resolved = canonicalize(entry.path())?; 32 | if resolved == executable_path.as_ref() { 33 | return Ok(Some(entry.path())); 34 | } 35 | } 36 | } 37 | } 38 | } 39 | Ok(None) 40 | } 41 | 42 | pub(crate) fn macos_exec_bundle() { 43 | if is_running_bundled() { 44 | return; 45 | } 46 | 47 | let path = get_executable_path(); 48 | if let Some(path) = path { 49 | let bundle_executable = find_bundle_executable(path); 50 | match bundle_executable { 51 | Ok(Some(bundle_executable)) => { 52 | let args: Vec = std::env::args().collect(); 53 | let err = exec::Command::new(bundle_executable).args(&args).exec(); 54 | warn!("Exec failed with: {:?}", err); 55 | } 56 | Ok(None) => {} 57 | Err(error) => { 58 | warn!("Could not find bundle: {:?}", error); 59 | } 60 | } 61 | } else { 62 | warn!("Could not determine process executable path"); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /nativeshell/src/util/cell.rs: -------------------------------------------------------------------------------- 1 | use std::cell::{Ref, RefCell, RefMut}; 2 | 3 | // Late initialized variable. It can only be set once and will panic if 4 | // used before initialization. 5 | pub struct Late { 6 | value: Option, 7 | } 8 | 9 | impl Late { 10 | pub fn new() -> Self { 11 | Self { value: None } 12 | } 13 | 14 | pub fn set(&mut self, value: T) { 15 | match self.value.as_ref() { 16 | Some(_) => panic!("Late variable can only be set once."), 17 | None => { 18 | self.value.replace(value); 19 | } 20 | } 21 | } 22 | } 23 | 24 | impl std::ops::Deref for Late { 25 | type Target = T; 26 | 27 | fn deref(&self) -> &T { 28 | self.value.as_ref().unwrap() 29 | } 30 | } 31 | 32 | impl std::ops::DerefMut for Late { 33 | fn deref_mut(&mut self) -> &mut T { 34 | self.value.as_mut().unwrap() 35 | } 36 | } 37 | 38 | // RefCell implementation that supports late initialization and can only be set once; 39 | // Panics if data is accessed before set has been called or if set is called more than once. 40 | #[derive(Clone)] 41 | pub struct LateRefCell { 42 | value: RefCell>, 43 | } 44 | 45 | impl LateRefCell { 46 | pub fn new() -> Self { 47 | Self { 48 | value: RefCell::new(None), 49 | } 50 | } 51 | 52 | pub fn set(&self, value: T) { 53 | let mut v = self.value.borrow_mut(); 54 | match v.as_ref() { 55 | Some(_) => { 56 | panic!("Value already set") 57 | } 58 | None => *v = Some(value), 59 | } 60 | } 61 | 62 | pub fn is_set(&self) -> bool { 63 | self.value.borrow().is_some() 64 | } 65 | 66 | pub fn clone_value(&self) -> T 67 | where 68 | T: Clone, 69 | { 70 | let value = self.value.borrow(); 71 | match &*value { 72 | Some(value) => value.clone(), 73 | None => { 74 | panic!("Value has not been set yet"); 75 | } 76 | } 77 | } 78 | 79 | pub fn borrow(&self) -> Ref { 80 | Ref::map(self.value.borrow(), |t| t.as_ref().unwrap()) 81 | } 82 | 83 | pub fn borrow_mut(&self) -> RefMut { 84 | RefMut::map(self.value.borrow_mut(), |t| t.as_mut().unwrap()) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/src/keyboard_map.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:nativeshell/src/api_model_internal.dart' as model; 3 | 4 | import 'event.dart'; 5 | import 'keyboard_map_internal.dart'; 6 | 7 | class KeyboardMap { 8 | // Event fired when current system keyboard layout changes. 9 | static final onChange = VoidEvent(); 10 | 11 | // Retrieves current keyboard map. 12 | static KeyboardMap current() => KeyboardMapManager.instance.currentMap; 13 | 14 | int? getPlatformKeyCode(KeyboardKey key) { 15 | if (key is PhysicalKeyboardKey) { 16 | return _physicalToKey[key.usbHidUsage]?.platform; 17 | } else if (key is LogicalKeyboardKey) { 18 | return _logicalToKey[key.keyId]?.platform; 19 | } else { 20 | return null; 21 | } 22 | } 23 | 24 | PhysicalKeyboardKey? getPhysicalKeyForPlatformKeyCode(int code) { 25 | final key = _platformToKey[code]; 26 | return key != null ? PhysicalKeyboardKey(key.physical) : null; 27 | } 28 | 29 | PhysicalKeyboardKey? getPhysicalKeyForLogicalKey( 30 | LogicalKeyboardKey logicalKey) { 31 | final key = _logicalToKey[logicalKey.keyId]; 32 | return key != null ? PhysicalKeyboardKey(key.physical) : null; 33 | } 34 | 35 | LogicalKeyboardKey? getLogicalKeyForPhysicalKey( 36 | PhysicalKeyboardKey physicalKey, { 37 | bool shift = false, 38 | bool alt = false, 39 | bool meta = false, 40 | }) { 41 | final key = _physicalToKey[physicalKey.usbHidUsage]; 42 | 43 | if (key == null) { 44 | return null; 45 | } 46 | 47 | if (meta && key.logicalMeta != null) { 48 | return LogicalKeyboardKey(key.logicalMeta!); 49 | } else if (shift && alt && key.logicalAltShift != null) { 50 | return LogicalKeyboardKey(key.logicalAltShift!); 51 | } else if (shift && !alt && key.logicalShift != null) { 52 | return LogicalKeyboardKey(key.logicalShift!); 53 | } else if (!shift && alt && key.logicalAlt != null) { 54 | return LogicalKeyboardKey(key.logicalAlt!); 55 | } else if (!shift && !alt && key.logical != null) { 56 | return LogicalKeyboardKey(key.logical!); 57 | } else { 58 | return null; 59 | } 60 | } 61 | 62 | final Map _platformToKey; 63 | final Map _physicalToKey; 64 | final Map _logicalToKey; 65 | 66 | KeyboardMap(this._platformToKey, this._physicalToKey, this._logicalToKey); 67 | } 68 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/linux/run_loop.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::{Cell, RefCell}, 3 | collections::HashMap, 4 | rc::Rc, 5 | time::Duration, 6 | }; 7 | 8 | use glib::{source_remove, timeout_add_local, Continue, MainContext, SourceId}; 9 | 10 | pub type HandleType = usize; 11 | pub const INVALID_HANDLE: HandleType = 0; 12 | 13 | pub struct PlatformRunLoop { 14 | next_handle: Cell, 15 | timers: Rc>>, 16 | } 17 | 18 | #[allow(unused_variables)] 19 | impl PlatformRunLoop { 20 | pub fn new() -> Self { 21 | Self { 22 | next_handle: Cell::new(INVALID_HANDLE + 1), 23 | timers: Rc::new(RefCell::new(HashMap::new())), 24 | } 25 | } 26 | 27 | pub fn unschedule(&self, handle: HandleType) { 28 | let source = self.timers.borrow_mut().remove(&handle); 29 | if let Some(source) = source { 30 | source_remove(source); 31 | } 32 | } 33 | 34 | fn next_handle(&self) -> HandleType { 35 | let r = self.next_handle.get(); 36 | self.next_handle.replace(r + 1); 37 | r 38 | } 39 | 40 | #[must_use] 41 | pub fn schedule(&self, in_time: Duration, callback: F) -> HandleType 42 | where 43 | F: FnOnce() + 'static, 44 | { 45 | let callback = Rc::new(RefCell::new(Some(callback))); 46 | let handle = self.next_handle(); 47 | 48 | let timers = self.timers.clone(); 49 | let source_id = timeout_add_local(in_time, move || { 50 | timers.borrow_mut().remove(&handle); 51 | let f = callback 52 | .borrow_mut() 53 | .take() 54 | .expect("Timer callback was called multiple times"); 55 | f(); 56 | Continue(false) 57 | }); 58 | self.timers.borrow_mut().insert(handle, source_id); 59 | handle 60 | } 61 | 62 | pub fn run(&self) { 63 | gtk::main(); 64 | } 65 | 66 | pub fn stop(&self) { 67 | gtk::main_quit(); 68 | } 69 | 70 | pub fn new_sender(&self) -> PlatformRunLoopSender { 71 | PlatformRunLoopSender {} 72 | } 73 | } 74 | 75 | #[derive(Clone)] 76 | pub struct PlatformRunLoopSender {} 77 | 78 | #[allow(unused_variables)] 79 | impl PlatformRunLoopSender { 80 | pub fn send(&self, callback: F) 81 | where 82 | F: FnOnce() + 'static + Send, 83 | { 84 | let context = MainContext::default(); 85 | context.invoke(callback); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/win32/engine.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CString, ptr}; 2 | 3 | use super::{ 4 | binary_messenger::PlatformBinaryMessenger, 5 | error::PlatformResult, 6 | flutter_sys::{ 7 | FlutterDesktopEngineCreate, FlutterDesktopEngineDestroy, FlutterDesktopEngineGetMessenger, 8 | FlutterDesktopEngineGetPluginRegistrar, FlutterDesktopEngineProperties, 9 | FlutterDesktopEngineRef, 10 | }, 11 | util::to_utf16, 12 | }; 13 | 14 | pub type PlatformEngineType = FlutterDesktopEngineRef; 15 | 16 | pub struct PlatformEngine { 17 | pub(crate) handle: PlatformEngineType, 18 | } 19 | 20 | pub struct PlatformPlugin { 21 | pub name: String, 22 | pub register_func: Option, 23 | } 24 | 25 | impl PlatformEngine { 26 | pub fn new(plugins: &[PlatformPlugin]) -> Self { 27 | let assets = to_utf16("data\\flutter_assets"); 28 | let icu = to_utf16("data\\icudtl.dat"); 29 | let aot = to_utf16("data\\app.so"); 30 | let properties = FlutterDesktopEngineProperties { 31 | assets_path: assets.as_ptr(), 32 | icu_data_path: icu.as_ptr(), 33 | aot_library_path: aot.as_ptr(), 34 | dart_entrypoint_argc: 0, 35 | dart_entrypoint_argv: ptr::null_mut(), 36 | }; 37 | 38 | let engine = unsafe { FlutterDesktopEngineCreate(&properties) }; 39 | 40 | // register plugins 41 | for plugin in plugins { 42 | let name = CString::new(plugin.name.as_str()).unwrap(); 43 | let registrar = 44 | unsafe { FlutterDesktopEngineGetPluginRegistrar(engine, name.as_ptr()) }; 45 | if let Some(register_func) = plugin.register_func { 46 | unsafe { 47 | register_func(registrar as *mut _); 48 | } 49 | } 50 | } 51 | Self { handle: engine } 52 | } 53 | 54 | pub fn new_binary_messenger(&self) -> PlatformBinaryMessenger { 55 | let messenger = unsafe { FlutterDesktopEngineGetMessenger(self.handle) }; 56 | PlatformBinaryMessenger::from_handle(messenger) 57 | } 58 | 59 | pub fn launch(&mut self) -> PlatformResult<()> { 60 | // This is a bit inconsistent; On windows engine is unconditionally launched from controller 61 | // unsafe { FlutterDesktopEngineRun(self.handle, ptr::null()); } 62 | Ok(()) 63 | } 64 | 65 | pub fn shut_down(&mut self) -> PlatformResult<()> { 66 | unsafe { 67 | FlutterDesktopEngineDestroy(self.handle); 68 | } 69 | Ok(()) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /nativeshell/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nativeshell" 3 | version = "0.1.12" 4 | authors = ["Matej Knopp "] 5 | edition = "2018" 6 | description = "NativeShell Rust package" 7 | license = "MIT OR Apache-2.0" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | serde = { version = "1.0.119", features = ["derive"] } 13 | serde_bytes = "0.11.5" 14 | serde_json = "1.0.59" 15 | log = "0.4.13" 16 | velcro = "0.5.3" 17 | diff = "0.1.12" 18 | libc = "0.2.94" 19 | futures = "0.3.17" 20 | async-trait = "0.1.51" 21 | once_cell = "1.8.0" 22 | 23 | [build-dependencies] 24 | cargo-emit = "0.2.1" 25 | nativeshell_build = { version="0.1.1", path = "../nativeshell_build" } 26 | serde = { version = "1.0.119", features = ["derive"] } 27 | serde_json = "1.0.59" 28 | anyhow = "1.0.41" 29 | 30 | [target.'cfg(target_os = "macos")'.dependencies] 31 | cocoa = "0.24" 32 | core-foundation = "0.9" 33 | core-graphics = "0.22" 34 | dispatch = "0.2.0" 35 | objc = "0.2.7" 36 | block = "0.1.6" 37 | exec = "0.3.1" 38 | process_path = "0.1.3" 39 | url = "2.2.1" 40 | 41 | [target.'cfg(target_os = "macos")'.build-dependencies] 42 | cc = "1.0" 43 | 44 | [target.'cfg(target_os = "windows")'.dependencies] 45 | utf16_lit = "2.0.1" 46 | const-cstr = "0.3.0" 47 | widestring = "0.5.1" 48 | byte-slice-cast = "1.0.0" 49 | detour = { version = "0.8.0", default-features = false } 50 | base64 = "0.13.0" 51 | 52 | [target.'cfg(target_os = "windows")'.dependencies.windows] 53 | version = "0.28.0" 54 | features = [ 55 | "alloc", 56 | "build", 57 | "Win32_Foundation", 58 | "Win32_Graphics_Dwm", 59 | "Win32_Graphics_Dxgi_Common", 60 | "Win32_Graphics_Gdi", 61 | "Win32_Storage_StructuredStorage", 62 | "Win32_System_Com_StructuredStorage", 63 | "Win32_System_Com", 64 | "Win32_System_DataExchange", 65 | "Win32_System_Diagnostics_Debug", 66 | "Win32_System_LibraryLoader", 67 | "Win32_System_Memory", 68 | "Win32_System_Ole", 69 | "Win32_System_SystemServices", 70 | "Win32_System_Threading", 71 | "Win32_UI_Controls", 72 | "Win32_UI_Input_KeyboardAndMouse", 73 | "Win32_UI_Shell", 74 | "Win32_UI_TextServices", 75 | "Win32_UI_WindowsAndMessaging", 76 | ] 77 | 78 | [target.'cfg(target_os = "linux")'.dependencies] 79 | glib = "0.14.0" 80 | gio = "0.14.0" 81 | glib-sys = "0.14.0" 82 | gio-sys = "0.14.0" 83 | gobject-sys = "0.14.0" 84 | gdk = "0.14.0" 85 | cairo-rs = { version = "0.14.0" } 86 | gtk = { version = "0.14.0", features = ["v3_22"] } 87 | gtk-sys = "0.14.0" 88 | gdk-sys = "0.14.0" 89 | url = "2.2.1" 90 | percent-encoding = "2.1.0" 91 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/src/menu_bar.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | import 'menu.dart'; 7 | import 'menu_bar_internal.dart'; 8 | import 'window.dart'; 9 | 10 | enum MenuItemState { 11 | regular, 12 | hovered, 13 | selected, 14 | disabled, 15 | } 16 | 17 | typedef MenuItemBuilder = Widget Function( 18 | BuildContext context, Widget child, MenuItemState state); 19 | 20 | class MenuBar extends StatelessWidget { 21 | const MenuBar({ 22 | Key? key, 23 | required this.menu, 24 | required this.itemBuilder, 25 | }) : super(key: key); 26 | 27 | final Menu menu; 28 | final MenuItemBuilder itemBuilder; 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | if (Platform.isMacOS) { 33 | return _MacOSMenuBar(menu: menu); 34 | } else { 35 | return MenuBarInternal( 36 | menu: menu, 37 | builder: itemBuilder, 38 | ); 39 | } 40 | } 41 | } 42 | 43 | class _MacOSMenuBar extends StatefulWidget { 44 | final Menu menu; 45 | 46 | const _MacOSMenuBar({ 47 | Key? key, 48 | required this.menu, 49 | }) : super(key: key); 50 | 51 | @override 52 | State createState() { 53 | return _MacOSMenuBarState(); 54 | } 55 | } 56 | 57 | class _MacOSMenuBarState extends State<_MacOSMenuBar> { 58 | @override 59 | void initState() { 60 | super.initState(); 61 | } 62 | 63 | @override 64 | void reassemble() { 65 | super.reassemble; 66 | widget.menu.update(); 67 | } 68 | 69 | @override 70 | void deactivate() { 71 | super.deactivate(); 72 | final window = Window.of(context); 73 | if (window.currentWindowMenu == widget.menu) { 74 | window.setWindowMenu(_previousMenu); 75 | } 76 | } 77 | 78 | @override 79 | void didUpdateWidget(covariant _MacOSMenuBar oldWidget) { 80 | super.didUpdateWidget(oldWidget); 81 | _firstBuild = true; 82 | setState(() {}); 83 | } 84 | 85 | void _updateMenu() async { 86 | final menu = await Window.of(context).setWindowMenu(widget.menu); 87 | // only remember the first 'original' menu; 88 | if (!_havePreviousMenu) { 89 | _previousMenu = menu; 90 | _havePreviousMenu = true; 91 | } 92 | } 93 | 94 | @override 95 | Widget build(BuildContext context) { 96 | if (_firstBuild) { 97 | _updateMenu(); 98 | } 99 | return Container(width: 0, height: 0); 100 | } 101 | 102 | bool _firstBuild = true; 103 | bool _havePreviousMenu = false; 104 | Menu? _previousMenu; 105 | } 106 | -------------------------------------------------------------------------------- /nativeshell/src/codec/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | 3 | pub use self::value::Value; 4 | pub mod value; 5 | 6 | mod message_channel; 7 | mod method_channel; 8 | mod sender; 9 | mod standard_codec; 10 | 11 | pub use message_channel::*; 12 | pub use method_channel::*; 13 | pub use sender::*; 14 | pub use standard_codec::*; 15 | 16 | pub struct MethodCall { 17 | pub method: String, 18 | pub args: V, 19 | } 20 | 21 | pub type MethodCallResult = Result>; 22 | 23 | #[derive(Debug, Clone)] 24 | pub struct MethodCallError { 25 | pub code: String, 26 | pub message: Option, 27 | pub details: V, 28 | } 29 | 30 | impl std::fmt::Display for MethodCallError { 31 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 32 | match &self.message { 33 | Some(message) => { 34 | write!(f, "{} ({})", message, self.code) 35 | } 36 | None => write!(f, "{}", self.code), 37 | } 38 | } 39 | } 40 | 41 | impl MethodCallError { 42 | pub fn from_code_message(code: &str, message: &str) -> Self 43 | where 44 | V: Default, 45 | { 46 | Self { 47 | code: code.into(), 48 | message: Some(message.into()), 49 | details: Default::default(), 50 | } 51 | } 52 | } 53 | 54 | impl From for MethodCallError 55 | where 56 | V: Default, 57 | { 58 | fn from(e: Error) -> Self { 59 | Self { 60 | code: format!("{:?}", e), 61 | message: Some(format!("{}", e)), 62 | details: Default::default(), 63 | } 64 | } 65 | } 66 | 67 | pub trait MessageCodec: Send + Sync { 68 | /// Methods for plain messages 69 | fn encode_message(&self, v: &V) -> Vec; 70 | fn decode_message(&self, buf: &[u8]) -> Option; 71 | } 72 | 73 | pub trait MethodCodec: Send + Sync { 74 | fn decode_method_call(&self, buf: &[u8]) -> Option>; 75 | fn encode_success_envelope(&self, v: &V) -> Vec; 76 | fn encode_error_envelope(&self, code: &str, message: Option<&str>, details: &V) -> Vec; 77 | 78 | fn encode_method_call_result(&self, response: &MethodCallResult) -> Vec { 79 | match response { 80 | MethodCallResult::Ok(data) => self.encode_success_envelope(data), 81 | MethodCallResult::Err(err) => { 82 | self.encode_error_envelope(&err.code, err.message.as_deref(), &err.details) 83 | } 84 | } 85 | } 86 | 87 | /// Methods for calling into dart 88 | fn encode_method_call(&self, v: &MethodCall) -> Vec; 89 | fn decode_envelope(&self, buf: &[u8]) -> Option>; 90 | } 91 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/linux/utils.rs: -------------------------------------------------------------------------------- 1 | use gdk::{Event, EventType, Window}; 2 | use glib::translate::{FromGlibPtrFull, ToGlibPtr, ToGlibPtrMut}; 3 | 4 | #[derive(PartialEq)] 5 | pub(super) enum SessionType { 6 | X11, 7 | Wayland, 8 | } 9 | 10 | pub(super) fn get_session_type() -> SessionType { 11 | let session_type = std::env::var("XDG_SESSION_TYPE").ok(); 12 | match session_type.as_deref() { 13 | Some("wayland") => SessionType::Wayland, 14 | _ => SessionType::X11, 15 | } 16 | } 17 | 18 | pub(super) fn synthetize_button_up(event: &Event) -> Event { 19 | if event.event_type() != EventType::ButtonPress { 20 | panic!("Invalid event type"); 21 | } 22 | let mut event = event.clone(); 23 | let e: *mut gdk_sys::GdkEvent = event.to_glib_none_mut().0; 24 | let e = unsafe { &mut *e }; 25 | e.type_ = gdk_sys::GDK_BUTTON_RELEASE; 26 | event 27 | } 28 | 29 | pub(super) fn synthetize_leave_event_from_motion(event: &Event) -> Event { 30 | if event.event_type() != EventType::MotionNotify { 31 | panic!("Invalid event type"); 32 | } 33 | let mut res = Event::new(EventType::LeaveNotify); 34 | let e: *mut gdk_sys::GdkEvent = res.to_glib_none_mut().0; 35 | let e = unsafe { &mut *e }; 36 | e.crossing.window = event.window().unwrap().to_glib_full(); 37 | e.crossing.subwindow = event.window().unwrap().to_glib_full(); 38 | e.crossing.send_event = 1; 39 | e.crossing.x = event.coords().unwrap().0; 40 | e.crossing.y = event.coords().unwrap().1; 41 | e.crossing.x_root = event.root_coords().unwrap().0; 42 | e.crossing.y_root = event.root_coords().unwrap().1; 43 | 44 | res.set_device(Some(&event.device().unwrap())); 45 | res 46 | } 47 | 48 | pub(super) fn translate_event_to_window(event: &Event, win: &Window) -> Event { 49 | let mut event = event.clone(); 50 | let e: *mut gdk_sys::GdkEvent = event.to_glib_none_mut().0; 51 | let e = unsafe { &mut *e }; 52 | if event.event_type() == EventType::MotionNotify { 53 | unsafe { Window::from_glib_full(e.motion.window) }; 54 | e.motion.window = win.to_glib_full(); 55 | let (_, win_x, win_y) = win.origin(); 56 | e.motion.x = unsafe { e.motion.x_root } - win_x as f64; 57 | e.motion.y = unsafe { e.motion.y_root } - win_y as f64; 58 | } 59 | if event.event_type() == EventType::EnterNotify || event.event_type() == EventType::LeaveNotify 60 | { 61 | unsafe { Window::from_glib_full(e.crossing.window) }; 62 | e.crossing.window = win.to_glib_full(); 63 | let (_, win_x, win_y) = win.origin(); 64 | e.crossing.x = unsafe { e.crossing.x_root } - win_x as f64; 65 | e.crossing.y = unsafe { e.crossing.y_root } - win_y as f64; 66 | } 67 | event 68 | } 69 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/macos/keyboard_map_sys.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::c_void, os::raw::c_ulong}; 2 | 3 | use core_foundation::{array::CFIndex, dictionary::CFDictionaryRef, string::CFStringRef}; 4 | 5 | pub type CFObject = *mut c_void; 6 | pub type CFNotificationCenterRef = CFObject; 7 | 8 | #[link(name = "Carbon", kind = "framework")] 9 | extern "C" { 10 | pub static kTISPropertyUnicodeKeyLayoutData: CFObject; 11 | pub static kTISNotifySelectedKeyboardInputSourceChanged: CFStringRef; 12 | pub fn TISCopyCurrentKeyboardLayoutInputSource() -> CFObject; 13 | pub fn TISCopyCurrentASCIICapableKeyboardLayoutInputSource() -> CFObject; 14 | pub fn TISGetInputSourceProperty(input_source: CFObject, property_key: CFObject) 15 | -> *mut c_void; 16 | 17 | pub fn LMGetKbdType() -> u32; 18 | 19 | pub fn UCKeyTranslate( 20 | layout_ptr: *mut c_void, 21 | virtual_key_code: u16, 22 | key_action: u16, 23 | modifier_key_state: u32, 24 | keyboard_type: u32, 25 | key_translate_options: u32, 26 | dead_code_state: *mut u32, 27 | max_string_length: c_ulong, 28 | actual_string_length: *mut c_ulong, 29 | unicode_string: *mut u16, 30 | ); 31 | } 32 | 33 | pub type CFNotificationCallback = Option< 34 | extern "C" fn( 35 | center: CFNotificationCenterRef, 36 | observer: *mut c_void, 37 | name: CFStringRef, 38 | object: *const c_void, 39 | userInfo: CFDictionaryRef, 40 | ), 41 | >; 42 | 43 | pub type CFNotificationSuspensionBehavior = CFIndex; 44 | pub const CFNotificationSuspensionBehaviorCoalesce: CFIndex = 2; 45 | 46 | extern "C" { 47 | pub fn CFNotificationCenterGetDistributedCenter() -> CFNotificationCenterRef; 48 | pub fn CFNotificationCenterAddObserver( 49 | center: CFNotificationCenterRef, 50 | observer: *const c_void, 51 | callBack: CFNotificationCallback, 52 | name: CFStringRef, 53 | object: *const c_void, 54 | suspensionBehavior: CFNotificationSuspensionBehavior, 55 | ); 56 | pub fn CFNotificationCenterRemoveObserver( 57 | center: CFNotificationCenterRef, 58 | observer: *const c_void, 59 | name: CFStringRef, 60 | object: *const c_void, 61 | ); 62 | } 63 | 64 | #[allow(non_upper_case_globals)] 65 | pub const kUCKeyActionDisplay: u16 = 3; 66 | #[allow(non_upper_case_globals)] 67 | pub const kUCKeyTranslateNoDeadKeysBit: u32 = 0; 68 | #[allow(non_upper_case_globals)] 69 | pub const kUCKeyTranslateNoDeadKeysMask: u32 = 1 << kUCKeyTranslateNoDeadKeysBit; 70 | #[allow(non_upper_case_globals)] 71 | pub const cmdKey: u32 = 256; 72 | #[allow(non_upper_case_globals)] 73 | pub const shiftKey: u32 = 512; 74 | #[allow(non_upper_case_globals)] 75 | pub const altKey: u32 = 2048; 76 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/macos/engine.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | binary_messenger::PlatformBinaryMessenger, 3 | error::{PlatformError, PlatformResult}, 4 | }; 5 | use crate::shell::platform::platform_impl::utils::{class_from_string, to_nsstring}; 6 | use cocoa::base::{id, nil, BOOL, NO}; 7 | use log::warn; 8 | use objc::{ 9 | class, msg_send, 10 | rc::{autoreleasepool, StrongPtr}, 11 | sel, sel_impl, 12 | }; 13 | 14 | pub type PlatformEngineType = StrongPtr; 15 | 16 | pub struct PlatformEngine { 17 | pub(crate) handle: PlatformEngineType, 18 | pub(super) view_controller: StrongPtr, 19 | } 20 | 21 | pub struct PlatformPlugin { 22 | pub name: String, 23 | pub class: String, 24 | } 25 | 26 | impl PlatformEngine { 27 | pub fn new(plugins: &[PlatformPlugin]) -> Self { 28 | autoreleasepool(|| unsafe { 29 | let class = class!(FlutterViewController); 30 | let view_controller: id = msg_send![class, alloc]; 31 | let view_controller = StrongPtr::new(msg_send![view_controller, initWithProject: nil]); 32 | let engine: id = msg_send![*view_controller, engine]; 33 | 34 | // register plugins with this engine 35 | for plugin in plugins { 36 | let class = class_from_string(&plugin.class); 37 | if class.is_null() { 38 | warn!( 39 | "Plugin {} for plugin {} not found", 40 | plugin.name, plugin.class 41 | ); 42 | } else { 43 | let registrar: id = 44 | msg_send![engine, registrarForPlugin: *to_nsstring(&plugin.name)]; 45 | let () = msg_send![class, registerWithRegistrar: registrar]; 46 | } 47 | } 48 | 49 | Self { 50 | handle: StrongPtr::retain(engine), 51 | view_controller, 52 | } 53 | }) 54 | } 55 | 56 | pub fn new_binary_messenger(&self) -> PlatformBinaryMessenger { 57 | autoreleasepool(|| unsafe { 58 | let messenger: id = msg_send![*self.handle, binaryMessenger]; 59 | PlatformBinaryMessenger::from_handle(StrongPtr::retain(messenger)) 60 | }) 61 | } 62 | 63 | pub fn launch(&mut self) -> PlatformResult<()> { 64 | let res: BOOL = 65 | autoreleasepool(|| unsafe { msg_send![*self.view_controller, launchEngine] }); 66 | if res == NO { 67 | Err(PlatformError::LaunchEngineFailure) 68 | } else { 69 | Ok(()) 70 | } 71 | } 72 | 73 | pub fn shut_down(&mut self) -> PlatformResult<()> { 74 | autoreleasepool(|| unsafe { 75 | let () = msg_send![*self.handle, shutDownEngine]; 76 | }); 77 | Ok(()) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/macos/hot_key_sys.rs: -------------------------------------------------------------------------------- 1 | use core_foundation::base::OSStatus; 2 | 3 | /* automatically generated by rust-bindgen 0.58.1 */ 4 | 5 | pub type EventTargetRef = *mut ::std::os::raw::c_void; 6 | pub type EventHandlerRef = *mut ::std::os::raw::c_void; 7 | pub type EventHandlerCallRef = *mut ::std::os::raw::c_void; 8 | pub type EventRef = *mut ::std::os::raw::c_void; 9 | pub type EventHandlerUPP = ::std::option::Option< 10 | unsafe extern "C" fn( 11 | inHandlerCallRef: EventHandlerCallRef, 12 | inEvent: EventRef, 13 | inUserData: *mut ::std::os::raw::c_void, 14 | ) -> OSStatus, 15 | >; 16 | pub type ItemCount = ::std::os::raw::c_ulong; 17 | pub type OSType = u32; 18 | #[repr(C)] 19 | #[allow(non_snake_case)] 20 | #[derive(Debug, Copy, Clone)] 21 | pub struct EventTypeSpec { 22 | pub eventClass: OSType, 23 | pub eventKind: u32, 24 | } 25 | #[allow(non_upper_case_globals)] 26 | pub const kEventClassKeyboard: OSType = 1801812322; 27 | #[allow(non_upper_case_globals)] 28 | pub const kEventHotKeyPressed: u32 = 5; 29 | extern "C" { 30 | pub fn InstallEventHandler( 31 | inTarget: EventTargetRef, 32 | inHandler: EventHandlerUPP, 33 | inNumTypes: ItemCount, 34 | inList: *const EventTypeSpec, 35 | inUserData: *mut ::std::os::raw::c_void, 36 | outRef: *mut EventHandlerRef, 37 | ) -> OSStatus; 38 | } 39 | extern "C" { 40 | pub fn RemoveEventHandler(inHandlerRef: EventHandlerRef) -> OSStatus; 41 | } 42 | pub type EventParamName = OSType; 43 | pub type EventParamType = OSType; 44 | pub type ByteCount = ::std::os::raw::c_ulong; 45 | #[allow(non_upper_case_globals)] 46 | pub const kEventParamDirectObject: EventParamName = 757935405; 47 | #[allow(non_upper_case_globals)] 48 | pub const typeEventHotKeyID: OSType = 1751869796; 49 | extern "C" { 50 | pub fn GetEventParameter( 51 | inEvent: EventRef, 52 | inName: EventParamName, 53 | inDesiredType: EventParamType, 54 | outActualType: *mut EventParamType, 55 | inBufferSize: ByteCount, 56 | outActualSize: *mut ByteCount, 57 | outData: *mut ::std::os::raw::c_void, 58 | ) -> OSStatus; 59 | } 60 | #[repr(C)] 61 | #[derive(Debug, Copy, Clone)] 62 | pub struct EventHotKeyID { 63 | pub signature: OSType, 64 | pub id: u32, 65 | } 66 | pub type OptionBits = u32; 67 | pub type EventHotKeyRef = *mut ::std::os::raw::c_void; 68 | extern "C" { 69 | pub fn RegisterEventHotKey( 70 | inHotKeyCode: u32, 71 | inHotKeyModifiers: u32, 72 | inHotKeyID: EventHotKeyID, 73 | inTarget: EventTargetRef, 74 | inOptions: OptionBits, 75 | outRef: *mut EventHotKeyRef, 76 | ) -> OSStatus; 77 | } 78 | extern "C" { 79 | pub fn UnregisterEventHotKey(inHotKey: EventHotKeyRef) -> OSStatus; 80 | } 81 | extern "C" { 82 | pub fn GetEventDispatcherTarget() -> EventTargetRef; 83 | } 84 | -------------------------------------------------------------------------------- /nativeshell/src/shell/keyboard_map_manager.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | collections::HashSet, 4 | rc::{Rc, Weak}, 5 | }; 6 | 7 | use crate::{ 8 | codec::value::to_value, 9 | util::{Late, OkLog}, 10 | }; 11 | 12 | use super::{ 13 | api_constants::{channel, method}, 14 | platform::keyboard_map::PlatformKeyboardMap, 15 | Context, EngineHandle, MethodCallHandler, MethodInvokerProvider, RegisteredMethodCallHandler, 16 | }; 17 | 18 | pub struct KeyboardMapManager { 19 | context: Context, 20 | pub(crate) platform_map: Late>, 21 | engines: HashSet, 22 | provider: Late, 23 | } 24 | 25 | pub trait KeyboardMapDelegate { 26 | fn keyboard_map_did_change(&self); 27 | } 28 | 29 | impl KeyboardMapManager { 30 | pub fn new(context: Context) -> RegisteredMethodCallHandler { 31 | Self { 32 | context: context.clone(), 33 | platform_map: Late::new(), 34 | engines: HashSet::new(), 35 | provider: Late::new(), 36 | } 37 | .register(context, channel::KEYBOARD_MAP_MANAGER) 38 | } 39 | } 40 | 41 | impl MethodCallHandler for KeyboardMapManager { 42 | fn on_method_call( 43 | &mut self, 44 | call: crate::codec::MethodCall, 45 | reply: crate::codec::MethodCallReply, 46 | engine: EngineHandle, 47 | ) { 48 | #[allow(clippy::single_match)] 49 | match call.method.as_str() { 50 | method::keyboard_map::GET => { 51 | self.engines.insert(engine); 52 | let layout = self.platform_map.get_current_map(); 53 | reply.send_ok(to_value(layout).unwrap()); 54 | } 55 | _ => {} 56 | } 57 | } 58 | 59 | fn assign_weak_self(&mut self, weak_self: Weak>) { 60 | let delegate: Weak> = weak_self; 61 | self.platform_map.set(Rc::new(PlatformKeyboardMap::new( 62 | self.context.clone(), 63 | delegate, 64 | ))); 65 | self.platform_map 66 | .assign_weak_self(Rc::downgrade(&self.platform_map)); 67 | } 68 | 69 | fn assign_invoker_provider(&mut self, provider: MethodInvokerProvider) { 70 | self.provider.set(provider); 71 | } 72 | 73 | // called when engine is about to be destroyed 74 | fn on_engine_destroyed(&mut self, engine: EngineHandle) { 75 | self.engines.remove(&engine); 76 | } 77 | } 78 | 79 | impl KeyboardMapDelegate for KeyboardMapManager { 80 | fn keyboard_map_did_change(&self) { 81 | let layout = self.platform_map.get_current_map(); 82 | let layout = to_value(layout).unwrap(); 83 | for engine in &self.engines { 84 | let invoker = self.provider.get_method_invoker_for_engine(*engine); 85 | invoker 86 | .call_method(method::keyboard_map::ON_CHANGED, layout.clone(), |_| {}) 87 | .ok_log(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/linux/size_widget.rs: -------------------------------------------------------------------------------- 1 | use std::{cmp::max, ffi::CString, mem, ptr}; 2 | 3 | use glib::{ 4 | translate::{FromGlibPtrFull, FromGlibPtrNone}, 5 | ObjectExt, 6 | }; 7 | use gtk::{prelude::WidgetExt, Widget}; 8 | 9 | unsafe extern "C" fn class_init(class: glib_sys::gpointer, _class_data: glib_sys::gpointer) { 10 | let widget_class = class as *mut gtk_sys::GtkWidgetClass; 11 | let widget_class = &mut *widget_class; 12 | widget_class.get_preferred_width = Some(get_preferred_width); 13 | widget_class.get_preferred_height = Some(get_preferred_height); 14 | } 15 | 16 | unsafe extern "C" fn instance_init( 17 | _instance: *mut gobject_sys::GTypeInstance, 18 | _instance_data: glib_sys::gpointer, 19 | ) { 20 | } 21 | 22 | fn size_widget_get_type() -> glib_sys::GType { 23 | static ONCE: ::std::sync::Once = ::std::sync::Once::new(); 24 | 25 | static mut TYPE: glib_sys::GType = 0; 26 | 27 | ONCE.call_once(|| unsafe { 28 | let name = CString::new("NativeShellSizeWidget").unwrap(); 29 | TYPE = gobject_sys::g_type_register_static_simple( 30 | gtk_sys::gtk_bin_get_type(), 31 | name.as_ptr(), 32 | mem::size_of::() as u32, 33 | Some(class_init), 34 | mem::size_of::() as u32, 35 | Some(instance_init), 36 | 0, 37 | ); 38 | }); 39 | 40 | unsafe { TYPE } 41 | } 42 | 43 | unsafe extern "C" fn get_preferred_width( 44 | widget: *mut gtk_sys::GtkWidget, 45 | minimum: *mut i32, 46 | natural: *mut i32, 47 | ) { 48 | let widget = Widget::from_glib_none(widget); 49 | let width: Option> = widget.data("nativeshell_minimum_width"); 50 | if let Some(width) = width.map(|w| *unsafe { w.as_ref() }) { 51 | *minimum = max(width, 1); 52 | *natural = max(width, 1); 53 | } else { 54 | *minimum = 1; 55 | *natural = 1; 56 | } 57 | } 58 | 59 | unsafe extern "C" fn get_preferred_height( 60 | widget: *mut gtk_sys::GtkWidget, 61 | minimum: *mut i32, 62 | natural: *mut i32, 63 | ) { 64 | let widget = Widget::from_glib_none(widget); 65 | let height: Option> = widget.data("nativeshell_minimum_height"); 66 | if let Some(height) = height.map(|h| *unsafe { h.as_ref() }) { 67 | *minimum = max(height, 1); 68 | *natural = max(height, 1); 69 | } else { 70 | *minimum = 1; 71 | *natural = 1; 72 | } 73 | } 74 | 75 | pub(super) fn create_size_widget() -> gtk::Widget { 76 | unsafe { 77 | let instance = gobject_sys::g_object_new(size_widget_get_type(), std::ptr::null_mut()); 78 | gobject_sys::g_object_ref_sink(instance); 79 | gtk::Widget::from_glib_full(instance as *mut _) 80 | } 81 | } 82 | 83 | pub(super) fn size_widget_set_min_size(widget: >k::Widget, width: i32, height: i32) { 84 | unsafe { 85 | widget.set_data("nativeshell_minimum_width", width); 86 | widget.set_data("nativeshell_minimum_height", height); 87 | } 88 | widget.queue_resize(); 89 | } 90 | -------------------------------------------------------------------------------- /nativeshell/src/codec/message_channel.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::shell::{BinaryMessengerReply, Context, ContextRef, EngineHandle, EngineManager}; 4 | 5 | use super::MessageCodec; 6 | 7 | pub struct MessageChannel 8 | where 9 | V: 'static, 10 | { 11 | context: Context, 12 | channel_name: String, 13 | engine_handle: EngineHandle, 14 | _data: PhantomData, 15 | } 16 | 17 | impl MessageChannel { 18 | pub fn new( 19 | context: &ContextRef, 20 | engine_handle: EngineHandle, 21 | channel_name: &str, 22 | codec: &'static dyn MessageCodec, 23 | callback: F, 24 | ) -> Self 25 | where 26 | F: Fn(V, MessageReply) + 'static, 27 | { 28 | Self::new_with_engine_manager( 29 | context.weak(), 30 | engine_handle, 31 | channel_name, 32 | codec, 33 | callback, 34 | &context.engine_manager.borrow(), 35 | ) 36 | } 37 | 38 | pub fn new_with_engine_manager( 39 | context: Context, 40 | engine_handle: EngineHandle, 41 | channel_name: &str, 42 | codec: &'static dyn MessageCodec, 43 | callback: F, 44 | engine_manager: &EngineManager, 45 | ) -> Self 46 | where 47 | F: Fn(V, MessageReply) + 'static, 48 | { 49 | let res = MessageChannel { 50 | context, 51 | channel_name: channel_name.into(), 52 | engine_handle, 53 | _data: PhantomData {}, 54 | }; 55 | 56 | let engine = engine_manager.get_engine(engine_handle); 57 | if let Some(engine) = engine { 58 | let codec = codec; 59 | engine 60 | .binary_messenger() 61 | .register_channel_handler(channel_name, move |data, reply| { 62 | let message = codec.decode_message(data).unwrap(); 63 | let reply = MessageReply { reply, codec }; 64 | callback(message, reply); 65 | }); 66 | } 67 | res 68 | } 69 | } 70 | 71 | // 72 | // 73 | // 74 | 75 | pub struct MessageReply 76 | where 77 | V: 'static, 78 | { 79 | reply: BinaryMessengerReply, 80 | codec: &'static dyn MessageCodec, 81 | } 82 | 83 | impl MessageReply { 84 | pub fn send(self, value: V) { 85 | let encoded = self.codec.encode_message(&value); 86 | self.reply.send(&encoded); 87 | } 88 | } 89 | 90 | impl Drop for MessageChannel { 91 | fn drop(&mut self) { 92 | if let Some(context) = self.context.get() { 93 | let engine_manager = context.engine_manager.borrow(); 94 | let engine = engine_manager.get_engine(self.engine_handle); 95 | if let Some(engine) = engine { 96 | engine 97 | .binary_messenger() 98 | .unregister_channel_handler(&self.channel_name); 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /nativeshell/src/util/capsule.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | sync::atomic::{AtomicUsize, Ordering}, 3 | thread, 4 | }; 5 | 6 | use crate::shell::RunLoopSender; 7 | 8 | // Thread bound capsule; Allows retrieving the value only on the thread 9 | // where it was stored. 10 | pub struct Capsule 11 | where 12 | T: 'static, 13 | { 14 | value: Option, 15 | thread_id: usize, 16 | sender: Option, 17 | } 18 | 19 | impl Capsule 20 | where 21 | T: 'static, 22 | { 23 | // Creates new capsule; If the value is not taken out of capsule, the 24 | // capsule must be dropped on same thread as it was created, otherwise 25 | // it will panic 26 | pub fn new(value: T) -> Self { 27 | Self { 28 | value: Some(value), 29 | thread_id: get_thread_id(), 30 | sender: None, 31 | } 32 | } 33 | 34 | // Creates new capsule, If the value is not taken out of capsule and the 35 | // capsule is dropped on different thread than where it was created, it will 36 | // be sent to the sender and dropped on the run loop thread 37 | pub fn new_with_sender(value: T, sender: RunLoopSender) -> Self { 38 | Self { 39 | value: Some(value), 40 | thread_id: get_thread_id(), 41 | sender: Some(sender), 42 | } 43 | } 44 | 45 | pub fn get_ref(&self) -> Option<&T> { 46 | if self.thread_id == get_thread_id() { 47 | self.value.as_ref() 48 | } else { 49 | None 50 | } 51 | } 52 | 53 | pub fn get_mut(&mut self) -> Option<&mut T> { 54 | if self.thread_id == get_thread_id() { 55 | self.value.as_mut() 56 | } else { 57 | None 58 | } 59 | } 60 | 61 | pub fn take(&mut self) -> Option { 62 | if self.thread_id == get_thread_id() { 63 | self.value.take() 64 | } else { 65 | None 66 | } 67 | } 68 | } 69 | 70 | impl Drop for Capsule { 71 | fn drop(&mut self) { 72 | // we still have value and capsule was dropped in other thread 73 | if self.value.is_some() && self.thread_id != get_thread_id() { 74 | if let Some(sender) = self.sender.as_ref() { 75 | let carry = Carry(self.value.take().unwrap()); 76 | let thread_id = self.thread_id; 77 | sender.send(move || { 78 | // make sure that sender sent us back to initial thread 79 | if thread_id != get_thread_id() { 80 | panic!("Capsule was created on different thread than sender target") 81 | } 82 | let _ = carry; 83 | }); 84 | } else if !thread::panicking() { 85 | panic!("Capsule was dropped on wrong thread with data still in it!"); 86 | } 87 | } 88 | } 89 | } 90 | 91 | unsafe impl Send for Capsule {} 92 | 93 | struct Carry(T); 94 | 95 | unsafe impl Send for Carry {} 96 | 97 | fn get_thread_id() -> usize { 98 | thread_local!(static THREAD_ID: usize = next_thread_id()); 99 | THREAD_ID.with(|&x| x) 100 | } 101 | 102 | fn next_thread_id() -> usize { 103 | static mut COUNTER: AtomicUsize = AtomicUsize::new(0); 104 | unsafe { COUNTER.fetch_add(1, Ordering::SeqCst) } 105 | } 106 | -------------------------------------------------------------------------------- /nativeshell/src/codec/method_channel.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::shell::{BinaryMessengerReply, Context, ContextRef, EngineHandle, EngineManager}; 4 | 5 | use super::{MethodCall, MethodCallError, MethodCallResult, MethodCodec}; 6 | 7 | // Low level interface to method channel on single engine 8 | pub struct EngineMethodChannel 9 | where 10 | V: 'static, 11 | { 12 | context: Context, 13 | channel_name: String, 14 | engine_handle: EngineHandle, 15 | _data: PhantomData, 16 | } 17 | 18 | impl EngineMethodChannel { 19 | pub fn new( 20 | context: ContextRef, 21 | engine_handle: EngineHandle, 22 | channel_name: &str, 23 | codec: &'static dyn MethodCodec, 24 | callback: F, 25 | ) -> Self 26 | where 27 | F: Fn(MethodCall, MethodCallReply) + 'static, 28 | { 29 | Self::new_with_engine_manager( 30 | context.weak(), 31 | engine_handle, 32 | channel_name, 33 | codec, 34 | callback, 35 | &context.engine_manager.borrow(), 36 | ) 37 | } 38 | 39 | pub fn new_with_engine_manager( 40 | context: Context, 41 | engine_handle: EngineHandle, 42 | channel_name: &str, 43 | codec: &'static dyn MethodCodec, 44 | callback: F, 45 | engine_manager: &EngineManager, 46 | ) -> Self 47 | where 48 | F: Fn(MethodCall, MethodCallReply) + 'static, 49 | { 50 | let res = EngineMethodChannel { 51 | context, 52 | channel_name: channel_name.into(), 53 | engine_handle, 54 | _data: PhantomData {}, 55 | }; 56 | 57 | let engine = engine_manager.get_engine(engine_handle); 58 | if let Some(engine) = engine { 59 | let codec = codec; 60 | engine 61 | .binary_messenger() 62 | .register_channel_handler(channel_name, move |data, reply| { 63 | let message = codec.decode_method_call(data).unwrap(); 64 | let reply = MethodCallReply { reply, codec }; 65 | callback(message, reply); 66 | }); 67 | } 68 | res 69 | } 70 | } 71 | 72 | pub struct MethodCallReply 73 | where 74 | V: 'static, 75 | { 76 | reply: BinaryMessengerReply, 77 | codec: &'static dyn MethodCodec, 78 | } 79 | 80 | impl MethodCallReply { 81 | pub fn send(self, value: MethodCallResult) { 82 | let encoded = self.codec.encode_method_call_result(&value); 83 | self.reply.send(&encoded); 84 | } 85 | 86 | pub fn send_ok(self, value: V) { 87 | self.send(MethodCallResult::Ok(value)) 88 | } 89 | 90 | pub fn send_error(self, code: &str, message: Option<&str>, details: V) { 91 | self.send(MethodCallResult::Err(MethodCallError { 92 | code: code.into(), 93 | message: message.map(|m| m.into()), 94 | details, 95 | })); 96 | } 97 | } 98 | 99 | impl Drop for EngineMethodChannel { 100 | fn drop(&mut self) { 101 | if let Some(context) = self.context.get() { 102 | let engine_manager = context.engine_manager.borrow(); 103 | let engine = engine_manager.get_engine(self.engine_handle); 104 | if let Some(engine) = engine { 105 | engine 106 | .binary_messenger() 107 | .unregister_channel_handler(&self.channel_name); 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/src/api_constants.dart: -------------------------------------------------------------------------------- 1 | class Channels { 2 | static final dispatcher = 'nativeshell/window-dispatcher'; 3 | 4 | // window sub channels 5 | static final windowManager = '.window.window-manager'; 6 | static final dropTarget = '.window.drop-target'; 7 | static final dragSource = '.window.drag-source'; 8 | 9 | static final menuManager = 'nativeshell/menu-manager'; 10 | static final keyboardMapManager = 'nativeshell/keyboard-map-manager'; 11 | static final hotKeyManager = 'nativeshell/hot-key-manager'; 12 | } 13 | 14 | class Events { 15 | static final windowInitialize = 'event:Window.initialize'; 16 | static final windowVisibilityChanged = 'event:Window.visibilityChanged'; 17 | static final windowCloseRequest = 'event:Window.closeRequest'; 18 | static final windowClose = 'event:Window.close'; 19 | } 20 | 21 | const currentApiVersion = 1; 22 | 23 | class Methods { 24 | // WindowManager 25 | static final windowManagerGetApiVersion = 'WindowManager.getApiVersion'; 26 | static final windowManagerCreateWindow = 'WindowManager.createWindow'; 27 | static final windowManagerInitWindow = 'WindowManager.initWindow'; 28 | 29 | // Window 30 | static final windowShow = 'Window.show'; 31 | static final windowShowModal = 'Window.showModal'; 32 | static final windowReadyToShow = 'Window.readyToShow'; 33 | static final windowHide = 'Window.hide'; 34 | static final windowActivate = 'Window.activate'; 35 | static final windowClose = 'Window.close'; 36 | static final windowCloseWithResult = 'Window.closeWithResult'; 37 | 38 | static final windowSetGeometry = 'Window.setGeometry'; 39 | static final windowGetGeometry = 'Window.getGeometry'; 40 | static final windowSupportedGeometry = 'Window.supportedGeometry'; 41 | 42 | static final windowSetStyle = 'Window.setStyle'; 43 | static final windowSetTitle = 'Window.setTitle'; 44 | static final windowPerformWindowDrag = 'Window.performWindowDrag'; 45 | 46 | static final windowShowPopupMenu = 'Window.showPopupMenu'; 47 | static final windowHidePopupMenu = 'Window.hidePopupMenu'; 48 | static final windowShowSystemMenu = 'Window.showSystemMenu'; 49 | static final windowSetWindowMenu = 'Window.setWindowMenu'; 50 | 51 | static final windowSavePositionToString = 'Window.savePositionToString'; 52 | static final windowRestorePositionFromString = 53 | 'Window.restorePositionFromString'; 54 | 55 | // Drag Driver 56 | static final dragDriverDraggingUpdated = 'DragDriver.draggingUpdated'; 57 | static final dragDriverDraggingExited = 'DragDriver.draggingExited'; 58 | static final dragDriverPerformDrop = 'DragDriver.performDrop'; 59 | 60 | // Drop Source 61 | static final dragSourceBeginDragSession = 'DragSource.beginDragSession'; 62 | static final dragSourceDragSessionEnded = 'DragSource.dragSessionEnded'; 63 | 64 | // Menu 65 | static final menuCreateOrUpdate = 'Menu.createOrUpdate'; 66 | static final menuDestroy = 'Menu.destroy'; 67 | static final menuOnAction = 'Menu.onAction'; 68 | static final menuOnOpen = 'Menu.onOpen'; 69 | static final menuSetAppMenu = 'Menu.setAppMenu'; 70 | 71 | // Menubar 72 | static final menubarMoveToPreviousMenu = 'Menubar.moveToPreviousMenu'; 73 | static final menubarMoveToNextMenu = 'Menubar.moveToNextMenu'; 74 | 75 | // KeyboardMap 76 | static final keyboardMapGet = 'KeyboardMap.get'; 77 | static final keyboardMapOnChanged = 'KeyboardMap.onChanged'; 78 | 79 | // HotKey 80 | static final hotKeyCreate = 'HotKey.create'; 81 | static final hotKeyDestroy = 'HotKey.destroy'; 82 | static final hotKeyOnPressed = 'HotKey.onPressed'; 83 | } 84 | 85 | class Keys { 86 | static final dragDataFiles = 'drag-data:internal:files'; 87 | static final dragDataURLs = 'drag-data:internal:urls'; 88 | } 89 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/win32/hot_key.rs: -------------------------------------------------------------------------------- 1 | use windows::Win32::{ 2 | Foundation::HWND, 3 | UI::{ 4 | Input::KeyboardAndMouse::{ 5 | MapVirtualKeyW, RegisterHotKey, UnregisterHotKey, HOT_KEY_MODIFIERS, MOD_ALT, 6 | MOD_CONTROL, MOD_SHIFT, MOD_WIN, 7 | }, 8 | WindowsAndMessaging::MAPVK_VSC_TO_VK, 9 | }, 10 | }; 11 | 12 | use super::run_loop::PlatformRunLoopHotKeyDelegate; 13 | 14 | use std::{cell::RefCell, collections::HashMap, rc::Weak}; 15 | 16 | use crate::shell::{ 17 | api_model::Accelerator, Context, EngineHandle, HotKeyHandle, HotKeyManagerDelegate, 18 | }; 19 | 20 | use super::error::PlatformResult; 21 | 22 | pub(crate) struct PlatformHotKeyManager { 23 | context: Context, 24 | delegate: Weak>, 25 | hot_keys: RefCell>, 26 | } 27 | 28 | impl PlatformRunLoopHotKeyDelegate for PlatformHotKeyManager { 29 | fn on_hot_key(&self, hot_key: i32) { 30 | let hot_key = HotKeyHandle(hot_key as i64); 31 | if let Some(engine) = self.hot_keys.borrow().get(&hot_key) { 32 | if let Some(delegate) = self.delegate.upgrade() { 33 | delegate.borrow().on_hot_key_pressed(hot_key, *engine); 34 | } 35 | } 36 | } 37 | } 38 | 39 | impl PlatformHotKeyManager { 40 | pub fn new(context: Context, delegate: Weak>) -> Self { 41 | Self { 42 | context, 43 | delegate, 44 | hot_keys: RefCell::new(HashMap::new()), 45 | } 46 | } 47 | 48 | pub fn assign_weak_self(&self, weak: Weak) { 49 | if let Some(context) = self.context.get() { 50 | context 51 | .run_loop 52 | .borrow() 53 | .platform_run_loop 54 | .set_hot_key_delegate(weak); 55 | } 56 | } 57 | 58 | fn hwnd(&self) -> HWND { 59 | if let Some(context) = self.context.get() { 60 | context.run_loop.borrow().platform_run_loop.hwnd() 61 | } else { 62 | HWND(0) 63 | } 64 | } 65 | 66 | pub fn create_hot_key( 67 | &self, 68 | accelerator: Accelerator, 69 | virtual_key: i64, 70 | handle: HotKeyHandle, 71 | engine: EngineHandle, 72 | ) -> PlatformResult<()> { 73 | let mut modifiers = HOT_KEY_MODIFIERS::default(); 74 | if accelerator.alt { 75 | modifiers |= MOD_ALT; 76 | } 77 | if accelerator.control { 78 | modifiers |= MOD_CONTROL; 79 | } 80 | if accelerator.shift { 81 | modifiers |= MOD_SHIFT; 82 | } 83 | if accelerator.meta { 84 | modifiers |= MOD_WIN; 85 | } 86 | self.hot_keys.borrow_mut().insert(handle, engine); 87 | unsafe { 88 | let vk = MapVirtualKeyW(virtual_key as u32, MAPVK_VSC_TO_VK); 89 | RegisterHotKey(self.hwnd(), handle.0 as i32, modifiers, vk as u32); 90 | } 91 | Ok(()) 92 | } 93 | 94 | pub fn destroy_hot_key(&self, handle: HotKeyHandle) -> PlatformResult<()> { 95 | unsafe { 96 | UnregisterHotKey(self.hwnd(), handle.0 as i32); 97 | } 98 | Ok(()) 99 | } 100 | 101 | pub fn engine_destroyed(&self, engine: EngineHandle) -> PlatformResult<()> { 102 | let hot_keys: Vec = self 103 | .hot_keys 104 | .borrow() 105 | .iter() 106 | .filter_map(|(key, e)| if e == &engine { Some(*key) } else { None }) 107 | .collect(); 108 | for key in hot_keys { 109 | self.destroy_hot_key(key)?; 110 | } 111 | Ok(()) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/linux/flutter_sys.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::{c_char, c_void}; 2 | 3 | use gobject_sys::{GObject, GObjectClass}; 4 | use gtk_sys::{GtkContainer, GtkContainerClass, GtkWidget}; 5 | 6 | #[repr(C)] 7 | #[derive(Debug, Clone, Copy)] 8 | pub struct FlDartProject { 9 | parent_instance: GObject, 10 | } 11 | 12 | #[repr(C)] 13 | #[derive(Debug, Clone, Copy)] 14 | pub struct FlDartProjectClass { 15 | parent_class: GObjectClass, 16 | } 17 | 18 | #[repr(C)] 19 | #[derive(Debug, Clone, Copy)] 20 | pub struct FlEngine { 21 | parent_instance: GObject, 22 | } 23 | 24 | #[repr(C)] 25 | #[derive(Debug, Clone, Copy)] 26 | pub struct FlEngineClass { 27 | parent_class: GObjectClass, 28 | } 29 | 30 | #[repr(C)] 31 | #[derive(Debug, Clone, Copy)] 32 | pub struct FlView { 33 | parent_instance: GtkContainer, 34 | } 35 | 36 | #[repr(C)] 37 | #[derive(Debug, Clone, Copy)] 38 | pub struct FlViewClass { 39 | parent_class: GtkContainerClass, 40 | } 41 | 42 | #[repr(C)] 43 | #[derive(Debug, Clone, Copy)] 44 | pub struct FlBinaryMessenger { 45 | parent_instance: GObject, 46 | } 47 | 48 | #[repr(C)] 49 | #[derive(Debug, Clone, Copy)] 50 | pub struct FlBinaryMessengerClass { 51 | parent_class: GObjectClass, 52 | } 53 | 54 | #[repr(C)] 55 | #[derive(Debug, Clone, Copy)] 56 | pub struct FlBinaryMessengerResponseHandle { 57 | parent_instance: GObject, 58 | } 59 | 60 | #[repr(C)] 61 | #[derive(Debug, Clone, Copy)] 62 | pub struct FlBinaryMessengerResponseHandleClass { 63 | parent_class: GObjectClass, 64 | } 65 | 66 | pub type FlBinaryMessengerMessageHandler = Option< 67 | unsafe extern "C" fn( 68 | messenger: *mut FlBinaryMessenger, 69 | channel: *const c_char, 70 | bytes: *mut glib_sys::GBytes, 71 | response_handle: *mut FlBinaryMessengerResponseHandle, 72 | user_data: glib_sys::gpointer, 73 | ), 74 | >; 75 | 76 | #[cfg(test)] 77 | extern "C" { 78 | pub fn fl_dart_project_new() -> *mut GObject; 79 | } 80 | 81 | // Only link flutter_linux_gtk when not building for tests 82 | #[cfg(not(test))] 83 | #[link(name = "flutter_linux_gtk")] 84 | extern "C" { 85 | pub fn fl_dart_project_new() -> *mut GObject; 86 | } 87 | 88 | extern "C" { 89 | pub fn fl_view_new(project: *mut FlDartProject) -> *mut GtkWidget; 90 | pub fn fl_view_get_engine(view: *mut FlView) -> *mut GObject; 91 | 92 | pub fn fl_engine_get_binary_messenger(engine: *mut FlEngine) -> *mut GObject; 93 | 94 | pub fn fl_binary_messenger_set_message_handler_on_channel( 95 | messenger: *mut FlBinaryMessenger, 96 | channel: *const c_char, 97 | handler: FlBinaryMessengerMessageHandler, 98 | user_data: glib_sys::gpointer, 99 | destroy_notify: glib_sys::GDestroyNotify, 100 | ); 101 | 102 | pub fn fl_binary_messenger_send_response( 103 | messenger: *mut FlBinaryMessenger, 104 | response_handle: *mut FlBinaryMessengerResponseHandle, 105 | response: *mut glib_sys::GBytes, 106 | error: *mut *mut glib_sys::GError, 107 | ) -> glib_sys::gboolean; 108 | 109 | pub fn fl_binary_messenger_send_on_channel( 110 | messenger: *mut FlBinaryMessenger, 111 | channel: *const c_char, 112 | message: *mut glib_sys::GBytes, 113 | cancellable: *mut gio_sys::GCancellable, 114 | callback: gio_sys::GAsyncReadyCallback, 115 | user_data: glib_sys::gpointer, 116 | ); 117 | 118 | pub fn fl_binary_messenger_send_on_channel_finish( 119 | messenger: *mut FlBinaryMessenger, 120 | result: *mut gio_sys::GAsyncResult, 121 | error: *mut *mut glib_sys::GError, 122 | ) -> *mut glib_sys::GBytes; 123 | 124 | pub fn fl_plugin_registry_get_registrar_for_plugin( 125 | registry: *mut FlView, 126 | name: *const c_char, 127 | ) -> *mut c_void; 128 | } 129 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/src/api_model_internal.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'dart:ui'; 3 | import 'api_model.dart'; 4 | import 'menu.dart'; 5 | 6 | class PopupMenuRequest { 7 | PopupMenuRequest({ 8 | required this.handle, 9 | required this.position, 10 | this.trackingRect, 11 | this.itemRect, 12 | this.preselectFirst = false, 13 | }); 14 | 15 | final MenuHandle handle; 16 | final Offset position; 17 | final Rect? trackingRect; 18 | final Rect? itemRect; 19 | final bool preselectFirst; 20 | 21 | dynamic serialize() => { 22 | 'handle': handle.value, 23 | 'position': position.serialize(), 24 | 'trackingRect': trackingRect?.serialize(), 25 | 'itemRect': itemRect?.serialize(), 26 | 'preselectFirst': preselectFirst, 27 | }; 28 | } 29 | 30 | class PopupMenuResponse { 31 | PopupMenuResponse({ 32 | required this.itemSelected, 33 | }); 34 | 35 | static PopupMenuResponse deserialize(dynamic value) { 36 | final map = value as Map; 37 | return PopupMenuResponse(itemSelected: map['itemSelected']); 38 | } 39 | 40 | dynamic serialize() => { 41 | 'itemSelected': itemSelected, 42 | }; 43 | 44 | @override 45 | String toString() => serialize().toString(); 46 | 47 | final bool itemSelected; 48 | } 49 | 50 | class HidePopupMenuRequest { 51 | HidePopupMenuRequest({ 52 | required this.handle, 53 | }); 54 | 55 | final MenuHandle handle; 56 | 57 | dynamic serialize() => { 58 | 'handle': handle.value, 59 | }; 60 | } 61 | 62 | class ImageData { 63 | ImageData({ 64 | required this.width, 65 | required this.height, 66 | required this.bytesPerRow, 67 | required this.data, 68 | this.devicePixelRatio, 69 | }); 70 | 71 | final int width; 72 | final int height; 73 | final int bytesPerRow; 74 | final Uint8List data; 75 | final double? devicePixelRatio; 76 | 77 | static Future fromImage( 78 | Image image, { 79 | double? devicePixelRatio, 80 | }) async { 81 | final bytes = await image.toByteData(format: ImageByteFormat.rawRgba); 82 | return ImageData( 83 | width: image.width, 84 | height: image.height, 85 | bytesPerRow: image.width * 4, 86 | data: bytes!.buffer.asUint8List(), 87 | devicePixelRatio: devicePixelRatio); 88 | } 89 | 90 | dynamic serialize() => { 91 | 'width': width, 92 | 'height': height, 93 | 'bytesPerRow': bytesPerRow, 94 | 'data': data, 95 | 'devicePixelRatio': devicePixelRatio, 96 | }; 97 | } 98 | 99 | class KeyboardKey { 100 | KeyboardKey({ 101 | required this.platform, 102 | required this.physical, 103 | this.logical, 104 | this.logicalShift, 105 | this.logicalAlt, 106 | this.logicalAltShift, 107 | this.logicalMeta, 108 | }); 109 | 110 | final int platform; 111 | final int physical; 112 | final int? logical; 113 | final int? logicalShift; 114 | final int? logicalAlt; 115 | final int? logicalAltShift; 116 | final int? logicalMeta; 117 | 118 | static KeyboardKey deserialize(dynamic value) { 119 | final map = value as Map; 120 | return KeyboardKey( 121 | platform: map['platform'], 122 | physical: map['physical'], 123 | logical: map['logical'], 124 | logicalShift: map['logicalShift'], 125 | logicalAlt: map['logicalAlt'], 126 | logicalAltShift: map['logicalAltShift'], 127 | logicalMeta: map['logicalMeta']); 128 | } 129 | } 130 | 131 | class KeyboardMap { 132 | KeyboardMap({ 133 | required this.keys, 134 | }); 135 | 136 | final List keys; 137 | 138 | static KeyboardMap deserialize(dynamic value) { 139 | final map = value as Map; 140 | final keys = map['keys'] as List; 141 | return KeyboardMap(keys: keys.map(KeyboardKey.deserialize).toList()); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/win32/util.rs: -------------------------------------------------------------------------------- 1 | use windows::{ 2 | core::{Interface, RawPtr, GUID, HRESULT}, 3 | Win32::{ 4 | Foundation::{GetLastError, BOOL, LPARAM, PWSTR}, 5 | System::{ 6 | Com::{CoCreateInstance, CLSCTX_ALL}, 7 | DataExchange::GetClipboardFormatNameW, 8 | Diagnostics::Debug::FACILITY_WIN32, 9 | }, 10 | }, 11 | }; 12 | 13 | use super::error::{PlatformError, PlatformResult}; 14 | 15 | pub(super) fn to_utf16(string: &str) -> Vec { 16 | let mut res: Vec = string.encode_utf16().collect(); 17 | res.push(0); 18 | res 19 | } 20 | 21 | /// # Safety 22 | /// 23 | /// Data must be properly aligned (see slice::from_raw_parts) 24 | pub unsafe fn as_u8_slice(p: &T) -> &[u8] { 25 | ::std::slice::from_raw_parts((p as *const T) as *const u8, ::std::mem::size_of::()) 26 | } 27 | 28 | pub fn get_raw_ptr(t: &T) -> usize { 29 | #[repr(transparent)] 30 | struct Extractor(usize); 31 | unsafe { 32 | let s = &*(t as *const _ as *const Extractor); 33 | s.0 34 | } 35 | } 36 | 37 | /// # Safety 38 | /// 39 | /// ptr must point to a valid COM object instance 40 | pub unsafe fn com_object_from_ptr(ptr: RawPtr) -> Option { 41 | if ptr.is_null() { 42 | None 43 | } else { 44 | #[repr(transparent)] 45 | struct ComObject(RawPtr); 46 | let e = ComObject(ptr); 47 | let t = &*(&e as *const _ as *const T); 48 | Some(t.clone()) 49 | } 50 | } 51 | 52 | pub fn clipboard_format_to_string(format: u32) -> String { 53 | let mut buf: [u16; 4096] = [0; 4096]; 54 | unsafe { 55 | let len = 56 | GetClipboardFormatNameW(format, PWSTR(buf.as_mut_ptr() as *mut _), buf.len() as i32); 57 | 58 | String::from_utf16_lossy(&buf[..len as usize]) 59 | } 60 | } 61 | 62 | pub trait BoolResultExt { 63 | fn as_platform_result(&self) -> PlatformResult<()>; 64 | } 65 | 66 | #[allow(non_snake_case)] 67 | fn HRESULT_FROM_WIN32(x: u32) -> u32 { 68 | if x as i32 <= 0 { 69 | x as u32 70 | } else { 71 | ((x & 0x0000FFFF) | (FACILITY_WIN32.0 << 16) | 0x80000000) as u32 72 | } 73 | } 74 | 75 | impl BoolResultExt for BOOL { 76 | fn as_platform_result(&self) -> PlatformResult<()> { 77 | if self.as_bool() { 78 | Ok(()) 79 | } else { 80 | let err = unsafe { GetLastError() }; 81 | let err = HRESULT_FROM_WIN32(err.0); 82 | let err = HRESULT(err); 83 | Err(PlatformError::WindowsError(err.into())) 84 | } 85 | } 86 | } 87 | 88 | pub fn create_instance(clsid: &GUID) -> windows::core::Result { 89 | unsafe { CoCreateInstance(clsid, None, CLSCTX_ALL) } 90 | } 91 | 92 | pub(super) fn direct_composition_supported() -> bool { 93 | // for now dsiable direct composition until flutter composition problems 94 | // are resolved 95 | false 96 | // unsafe { 97 | // let handle = GetModuleHandleW("dcomp.dll"); 98 | // if handle != 0 { 99 | // GetProcAddress(handle, "DCompositionCreateDevice").is_some() 100 | // } else { 101 | // false 102 | // } 103 | // } 104 | } 105 | 106 | #[inline] 107 | #[allow(non_snake_case)] 108 | pub fn MAKELONG(lo: u16, hi: u16) -> u32 { 109 | (lo as u32) | ((hi as u32) << 16) 110 | } 111 | 112 | #[inline] 113 | #[allow(non_snake_case)] 114 | pub fn LOWORD(l: u32) -> u16 { 115 | (l & 0xffff) as u16 116 | } 117 | 118 | #[inline] 119 | #[allow(non_snake_case)] 120 | pub fn HIWORD(l: u32) -> u16 { 121 | ((l >> 16) & 0xffff) as u16 122 | } 123 | 124 | #[inline] 125 | #[allow(non_snake_case)] 126 | pub fn GET_X_LPARAM(lp: LPARAM) -> i32 { 127 | LOWORD(lp.0 as u32) as i32 128 | } 129 | 130 | #[inline] 131 | #[allow(non_snake_case)] 132 | pub fn GET_Y_LPARAM(lp: LPARAM) -> i32 { 133 | HIWORD(lp.0 as u32) as i32 134 | } 135 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/null/window.rs: -------------------------------------------------------------------------------- 1 | use std::rc::{Rc, Weak}; 2 | 3 | use crate::{ 4 | codec::Value, 5 | shell::{ 6 | api_model::{ 7 | DragEffect, DragRequest, PopupMenuRequest, PopupMenuResponse, WindowGeometry, 8 | WindowGeometryFlags, WindowGeometryRequest, WindowStyle, 9 | }, 10 | Context, PlatformWindowDelegate, 11 | }, 12 | }; 13 | 14 | use super::{ 15 | engine::PlatformEngine, 16 | error::{PlatformError, PlatformResult}, 17 | menu::PlatformMenu, 18 | }; 19 | 20 | pub type PlatformWindowType = isize; 21 | 22 | pub struct PlatformWindow {} 23 | 24 | #[allow(unused_variables)] 25 | impl PlatformWindow { 26 | pub fn new( 27 | context: Context, 28 | delegate: Weak, 29 | parent: Option>, 30 | ) -> Self { 31 | Self {} 32 | } 33 | 34 | pub fn assign_weak_self(&self, weak: Weak, engine: &PlatformEngine) {} 35 | 36 | pub fn get_platform_window(&self) -> PlatformWindowType { 37 | Default::default() 38 | } 39 | 40 | pub fn show(&self) -> PlatformResult<()> { 41 | Err(PlatformError::NotImplemented) 42 | } 43 | 44 | pub fn ready_to_show(&self) -> PlatformResult<()> { 45 | Err(PlatformError::NotImplemented) 46 | } 47 | 48 | pub fn close(&self) -> PlatformResult<()> { 49 | Err(PlatformError::NotImplemented) 50 | } 51 | 52 | pub fn close_with_result(&self, result: Value) -> PlatformResult<()> { 53 | Err(PlatformError::NotImplemented) 54 | } 55 | 56 | pub fn hide(&self) -> PlatformResult<()> { 57 | Err(PlatformError::NotImplemented) 58 | } 59 | 60 | pub fn activate(&self) -> PlatformResult { 61 | Err(PlatformError::NotImplemented) 62 | } 63 | 64 | pub fn show_modal(&self, done_callback: F) 65 | where 66 | F: FnOnce(PlatformResult) + 'static, 67 | { 68 | done_callback(Err(PlatformError::NotImplemented)) 69 | } 70 | 71 | pub fn set_geometry( 72 | &self, 73 | geometry: WindowGeometryRequest, 74 | ) -> PlatformResult { 75 | Err(PlatformError::NotImplemented) 76 | } 77 | 78 | pub fn get_geometry(&self) -> PlatformResult { 79 | Err(PlatformError::NotImplemented) 80 | } 81 | 82 | pub fn supported_geometry(&self) -> PlatformResult { 83 | Err(PlatformError::NotImplemented) 84 | } 85 | 86 | pub fn set_title(&self, title: String) -> PlatformResult<()> { 87 | Err(PlatformError::NotImplemented) 88 | } 89 | 90 | pub fn save_position_to_string(&self) -> PlatformResult { 91 | Ok(String::new()) 92 | } 93 | 94 | pub fn restore_position_from_string(&self, position: String) -> PlatformResult<()> { 95 | Ok(()) 96 | } 97 | 98 | pub fn set_style(&self, style: WindowStyle) -> PlatformResult<()> { 99 | Err(PlatformError::NotImplemented) 100 | } 101 | 102 | pub fn perform_window_drag(&self) -> PlatformResult<()> { 103 | Err(PlatformError::NotImplemented) 104 | } 105 | 106 | pub fn begin_drag_session(&self, request: DragRequest) -> PlatformResult<()> { 107 | Err(PlatformError::NotImplemented) 108 | } 109 | 110 | pub fn set_pending_effect(&self, effect: DragEffect) {} 111 | 112 | pub fn show_popup_menu(&self, menu: Rc, request: PopupMenuRequest, on_done: F) 113 | where 114 | F: FnOnce(PlatformResult) + 'static, 115 | { 116 | on_done(Err(PlatformError::NotImplemented)) 117 | } 118 | 119 | pub fn hide_popup_menu(&self, menu: Rc) -> PlatformResult<()> { 120 | Err(PlatformError::NotImplemented) 121 | } 122 | 123 | pub fn show_system_menu(&self) -> PlatformResult<()> { 124 | Err(PlatformError::NotImplemented) 125 | } 126 | 127 | pub fn set_window_menu(&self, menu: Option>) -> PlatformResult<()> { 128 | Err(PlatformError::NotImplemented) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/src/drag_session.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:ui' as ui; 3 | 4 | import 'package:flutter/rendering.dart'; 5 | import 'package:flutter/widgets.dart'; 6 | 7 | import 'api_constants.dart'; 8 | import 'api_model.dart'; 9 | import 'drag_drop.dart'; 10 | import 'util.dart'; 11 | import 'window.dart'; 12 | import 'window_method_channel.dart'; 13 | 14 | final _dragSourceChannel = WindowMethodChannel(Channels.dragSource); 15 | 16 | class DragException implements Exception { 17 | final String message; 18 | DragException(this.message); 19 | } 20 | 21 | class DragSession { 22 | static DragSession? currentSession() { 23 | return _DragSessionManager.instance.activeSession; 24 | } 25 | 26 | static Future beginWithContext({ 27 | required BuildContext context, 28 | required DragData data, 29 | required Set allowedEffects, 30 | }) async { 31 | final renderObject_ = context.findRenderObject(); 32 | final renderObject = renderObject_ is RenderRepaintBoundary 33 | ? renderObject_ 34 | : context.findAncestorRenderObjectOfType(); 35 | 36 | if (renderObject == null) { 37 | throw DragException("Couldn't find any repaint boundary ancestor"); 38 | } 39 | 40 | final pr = MediaQuery.of(context).devicePixelRatio; 41 | final snapshot = await renderObject.toImage(pixelRatio: pr); 42 | final rect = MatrixUtils.transformRect(renderObject.getTransformTo(null), 43 | Rect.fromLTWH(0, 0, renderObject.size.width, renderObject.size.height)); 44 | return DragSession.beginWithImage( 45 | window: Window.of(context), 46 | image: snapshot, 47 | rect: rect, 48 | data: data, 49 | allowedEffects: allowedEffects); 50 | } 51 | 52 | static Future beginWithImage({ 53 | required LocalWindow window, 54 | required ui.Image image, 55 | required Rect rect, 56 | required DragData data, 57 | required Set allowedEffects, 58 | }) async { 59 | final bytes = await image.toByteData(format: ui.ImageByteFormat.rawRgba); 60 | 61 | await _dragSourceChannel 62 | .invokeMethod(window.handle, Methods.dragSourceBeginDragSession, { 63 | 'image': { 64 | 'width': image.width, 65 | 'height': image.height, 66 | 'bytesPerRow': image.width * 4, 67 | 'data': bytes!.buffer.asUint8List() 68 | }, 69 | 'rect': rect.serialize(), 70 | 'data': data.serialize(), 71 | 'allowedEffects': 72 | allowedEffects.map((e) => enumToString(e)).toList(), 73 | }); 74 | 75 | final res = DragSession(); 76 | _DragSessionManager.instance.registerSession(res); 77 | return res; 78 | } 79 | 80 | Future waitForResult() async { 81 | if (_result != null) { 82 | return _result!; 83 | } else { 84 | return _completer.future; 85 | } 86 | } 87 | 88 | void _setResult(DragEffect result) { 89 | _result = result; 90 | _completer.complete(_result); 91 | } 92 | 93 | DragEffect? _result; 94 | final _completer = Completer(); 95 | } 96 | 97 | class _DragSessionManager { 98 | static final instance = _DragSessionManager(); 99 | 100 | _DragSessionManager() { 101 | _dragSourceChannel.setMethodCallHandler(_onMethodCall); 102 | } 103 | 104 | Future _onMethodCall(WindowMethodCall call) async { 105 | if (call.method == Methods.dragSourceDragSessionEnded) { 106 | final result = 107 | enumFromString(DragEffect.values, call.arguments, DragEffect.None); 108 | assert(_activeSessions.isNotEmpty, 109 | 'Received drag session notification without active drag session.'); 110 | final session = _activeSessions.removeAt(0); 111 | session._setResult(result); 112 | } 113 | } 114 | 115 | DragSession? get activeSession => 116 | _activeSessions.isEmpty ? null : _activeSessions.last; 117 | 118 | void registerSession(DragSession session) { 119 | _activeSessions.add(session); 120 | } 121 | 122 | // It is possible to have more than one active session; MacOS drag session finished 123 | // notification can be delayed so we might have nother session already in progress; 124 | // Last value is current session 125 | final _activeSessions = []; 126 | } 127 | -------------------------------------------------------------------------------- /nativeshell/keyboard_map/gen_keyboard_map.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{BTreeMap, HashMap}, 3 | fs::{self, File}, 4 | io::Write, 5 | path::PathBuf, 6 | }; 7 | 8 | #[derive(serde::Deserialize, Debug)] 9 | #[serde(rename_all = "camelCase")] 10 | #[allow(dead_code)] 11 | struct PhysicalKeyEntry { 12 | names: HashMap, 13 | scan_codes: HashMap, // number or array 14 | key_codes: Option>, 15 | } 16 | 17 | #[derive(serde::Deserialize, Debug)] 18 | #[serde(rename_all = "camelCase")] 19 | #[allow(dead_code)] 20 | struct LogicalKeyEntry { 21 | name: String, 22 | value: i64, 23 | key_label: Option, 24 | names: HashMap>, 25 | values: Option>>, 26 | } 27 | 28 | #[derive(Debug)] 29 | struct KeyData { 30 | name: String, 31 | platform: i64, 32 | physical: i64, 33 | logical: Option, 34 | fallback: Option, 35 | } 36 | 37 | fn first_number(value: &serde_json::Value) -> Option { 38 | match value { 39 | serde_json::Value::Number(n) => n.as_i64(), 40 | serde_json::Value::Array(a) => a[0].as_i64(), 41 | _ => None, 42 | } 43 | } 44 | 45 | pub fn generate_keyboard_map(platform_name: &str) -> anyhow::Result<()> { 46 | let root_dir: PathBuf = std::env::var("CARGO_MANIFEST_DIR")?.into(); 47 | let out_dir: PathBuf = std::env::var("OUT_DIR")?.into(); 48 | 49 | let codes_dir = root_dir.join("keyboard_map"); 50 | let physical = fs::read_to_string(codes_dir.join("physical_key_data.json"))?; 51 | let logical = fs::read_to_string(codes_dir.join("logical_key_data.json"))?; 52 | 53 | let physical_keys: HashMap = serde_json::from_str(&physical)?; 54 | let logical_keys: HashMap = serde_json::from_str(&logical)?; 55 | 56 | let mut key_data = BTreeMap::::new(); 57 | 58 | let logical_platform_name: &str = match platform_name { 59 | "linux" => "gtk", 60 | name => name, 61 | }; 62 | 63 | let physical_platform_name: &str = match platform_name { 64 | "linux" => "xkb", 65 | name => name, 66 | }; 67 | 68 | for v in physical_keys.values() { 69 | if let (Some(platform), Some(usb)) = ( 70 | v.scan_codes 71 | .get(physical_platform_name) 72 | .and_then(first_number), 73 | v.scan_codes.get("usb").and_then(first_number), 74 | ) { 75 | let name = v.names.get("name").unwrap(); 76 | let mut logical = None::; 77 | let mut fallback = None::; 78 | if let Some(logical_key) = logical_keys.get(name) { 79 | fallback = Some(logical_key.value); 80 | if let Some(values) = &logical_key.values { 81 | if let Some(values) = values.get(logical_platform_name) { 82 | if !values.is_empty() { 83 | logical = Some(logical_key.value); 84 | } 85 | } 86 | } 87 | } 88 | 89 | key_data.insert( 90 | platform, 91 | KeyData { 92 | name: name.into(), 93 | platform, 94 | physical: usb, 95 | logical, 96 | fallback, // US layout fallback 97 | }, 98 | ); 99 | } 100 | } 101 | 102 | let gen_path = out_dir.join("generated_keyboard_map.rs"); 103 | let mut file = File::create(gen_path)?; 104 | writeln!(file, "#[allow(dead_code)]")?; 105 | writeln!( 106 | file, 107 | "struct KeyMapEntry {{ platform: i64, physical: i64, logical: Option, fallback: Option }}" 108 | )?; 109 | writeln!(file)?; 110 | writeln!(file, "fn get_key_map() -> Vec {{")?; 111 | writeln!(file, " vec![")?; 112 | for v in key_data.values() { 113 | writeln!( 114 | file, 115 | " KeyMapEntry {{ platform: {}, physical: {}, logical: {:?}, fallback: {:?} }}, // {}", 116 | v.platform, v.physical, v.logical, v.fallback, v.name, 117 | )?; 118 | } 119 | writeln!(file, " ]")?; 120 | writeln!(file, "}}")?; 121 | 122 | Ok(()) 123 | } 124 | -------------------------------------------------------------------------------- /nativeshell/src/shell/hot_key_manager.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | rc::{Rc, Weak}, 4 | }; 5 | 6 | use crate::{ 7 | codec::{ 8 | value::{from_value, to_value}, 9 | MethodCall, MethodCallReply, MethodCallResult, Value, 10 | }, 11 | util::{Late, OkLog}, 12 | Error, Result, 13 | }; 14 | 15 | use super::{ 16 | api_constants::{channel, method}, 17 | api_model::{HotKeyCreateRequest, HotKeyDestroyRequest, HotKeyPressed}, 18 | platform::hot_key::PlatformHotKeyManager, 19 | Context, EngineHandle, MethodCallHandler, MethodInvokerProvider, RegisteredMethodCallHandler, 20 | }; 21 | 22 | pub struct HotKeyManager { 23 | context: Context, 24 | platform_manager: Late>, 25 | next_handle: HotKeyHandle, 26 | invoker_provider: Late, 27 | } 28 | 29 | #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)] 30 | pub struct HotKeyHandle(pub(crate) i64); 31 | 32 | pub trait HotKeyManagerDelegate { 33 | fn on_hot_key_pressed(&self, handle: HotKeyHandle, engine: EngineHandle); 34 | } 35 | 36 | impl HotKeyManager { 37 | pub(super) fn new(context: Context) -> RegisteredMethodCallHandler { 38 | Self { 39 | context: context.clone(), 40 | platform_manager: Late::new(), 41 | next_handle: HotKeyHandle(1), 42 | invoker_provider: Late::new(), 43 | } 44 | .register(context, channel::HOT_KEY_MANAGER) 45 | } 46 | 47 | fn on_create( 48 | &mut self, 49 | request: HotKeyCreateRequest, 50 | engine: EngineHandle, 51 | ) -> Result { 52 | let handle = self.next_handle; 53 | self.next_handle.0 += 1; 54 | 55 | self.platform_manager 56 | .create_hot_key(request.accelerator, request.platform_key, handle, engine) 57 | .map_err(Error::from)?; 58 | 59 | Ok(handle) 60 | } 61 | 62 | fn map_result(result: Result) -> MethodCallResult 63 | where 64 | T: serde::Serialize, 65 | { 66 | result.map(|v| to_value(v).unwrap()).map_err(|e| e.into()) 67 | } 68 | } 69 | 70 | impl HotKeyManagerDelegate for HotKeyManager { 71 | fn on_hot_key_pressed(&self, handle: HotKeyHandle, engine: EngineHandle) { 72 | let invoker = self.invoker_provider.get_method_invoker_for_engine(engine); 73 | invoker 74 | .call_method( 75 | method::hot_key::ON_PRESSED, 76 | to_value(&HotKeyPressed { handle }).unwrap(), 77 | |_| {}, 78 | ) 79 | .ok_log(); 80 | } 81 | } 82 | 83 | impl MethodCallHandler for HotKeyManager { 84 | fn on_method_call( 85 | &mut self, 86 | call: MethodCall, 87 | reply: MethodCallReply, 88 | engine: EngineHandle, 89 | ) { 90 | match call.method.as_str() { 91 | method::hot_key::CREATE => { 92 | let request: HotKeyCreateRequest = from_value(&call.args).unwrap(); 93 | let res = self.on_create(request, engine); 94 | reply.send(Self::map_result(res)); 95 | } 96 | method::hot_key::DESTROY => { 97 | let request: HotKeyDestroyRequest = from_value(&call.args).unwrap(); 98 | let res = self 99 | .platform_manager 100 | .destroy_hot_key(request.handle) 101 | .map_err(Error::from); 102 | reply.send(Self::map_result(res)); 103 | } 104 | _ => {} 105 | } 106 | } 107 | 108 | fn assign_weak_self(&mut self, weak_self: std::rc::Weak>) { 109 | let delegate: Weak> = weak_self; 110 | self.platform_manager 111 | .set(Rc::new(PlatformHotKeyManager::new( 112 | self.context.clone(), 113 | delegate, 114 | ))); 115 | self.platform_manager 116 | .assign_weak_self(Rc::downgrade(&self.platform_manager)); 117 | } 118 | 119 | fn assign_invoker_provider(&mut self, provider: MethodInvokerProvider) { 120 | self.invoker_provider.set(provider); 121 | } 122 | 123 | fn on_engine_destroyed(&mut self, engine: EngineHandle) { 124 | self.platform_manager.engine_destroyed(engine).ok_log(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/linux/menu_item.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CString, mem}; 2 | 3 | use glib::{ 4 | translate::{FromGlibPtrFull, ToGlibPtr}, 5 | Cast, 6 | }; 7 | use gtk::prelude::CheckMenuItemExt; 8 | 9 | // GtkCheckMenuItem has a 'feature' where it unconditionally changes check status on activation. 10 | // We want to be more explicit about it - activation should not change check status 11 | // 12 | // Here the activate class method is replaced by one from GtkMenuItemClass 13 | unsafe extern "C" fn class_init(class: glib_sys::gpointer, _class_data: glib_sys::gpointer) { 14 | let our_class = class as *mut gtk_sys::GtkMenuItemClass; 15 | let our_class = &mut *our_class; 16 | 17 | let name = CString::new("GtkMenuItem").unwrap(); 18 | let menu_item_type = gobject_sys::g_type_from_name(name.as_ptr()); 19 | 20 | let menu_item_class = 21 | gobject_sys::g_type_class_peek(menu_item_type) as *mut gtk_sys::GtkMenuItemClass; 22 | let menu_item_class = &*menu_item_class; 23 | 24 | our_class.activate = menu_item_class.activate; 25 | } 26 | 27 | unsafe extern "C" fn instance_init( 28 | _instance: *mut gobject_sys::GTypeInstance, 29 | _instance_data: glib_sys::gpointer, 30 | ) { 31 | } 32 | 33 | // CheckMenuItem is not subclassable in Gtk-rs, need to do it manually 34 | fn check_menu_item_get_type() -> glib_sys::GType { 35 | static ONCE: ::std::sync::Once = ::std::sync::Once::new(); 36 | 37 | static mut TYPE: glib_sys::GType = 0; 38 | 39 | ONCE.call_once(|| unsafe { 40 | let name = CString::new("NativeShellCheckMenuItem").unwrap(); 41 | TYPE = gobject_sys::g_type_register_static_simple( 42 | gtk_sys::gtk_check_menu_item_get_type(), 43 | name.as_ptr(), 44 | mem::size_of::() as u32, 45 | Some(class_init), 46 | mem::size_of::() as u32, 47 | Some(instance_init), 48 | 0, 49 | ); 50 | }); 51 | 52 | unsafe { TYPE } 53 | } 54 | 55 | fn radio_menu_item_get_type() -> glib_sys::GType { 56 | static ONCE: ::std::sync::Once = ::std::sync::Once::new(); 57 | 58 | static mut TYPE: glib_sys::GType = 0; 59 | 60 | ONCE.call_once(|| unsafe { 61 | let name = CString::new("NativeShellRadioMenuItem").unwrap(); 62 | TYPE = gobject_sys::g_type_register_static_simple( 63 | gtk_sys::gtk_radio_menu_item_get_type(), 64 | name.as_ptr(), 65 | mem::size_of::() as u32, 66 | Some(class_init), 67 | mem::size_of::() as u32, 68 | Some(instance_init), 69 | 0, 70 | ); 71 | }); 72 | 73 | unsafe { TYPE } 74 | } 75 | 76 | pub(super) fn create_check_menu_item() -> gtk::CheckMenuItem { 77 | unsafe { 78 | let instance = gobject_sys::g_object_new(check_menu_item_get_type(), std::ptr::null_mut()); 79 | gobject_sys::g_object_ref_sink(instance); 80 | gtk::CheckMenuItem::from_glib_full(instance as *mut _) 81 | } 82 | } 83 | 84 | pub(super) fn create_radio_menu_item() -> gtk::RadioMenuItem { 85 | unsafe { 86 | let instance = gobject_sys::g_object_new(radio_menu_item_get_type(), std::ptr::null_mut()); 87 | gobject_sys::g_object_ref_sink(instance); 88 | gtk::RadioMenuItem::from_glib_full(instance as *mut _) 89 | } 90 | } 91 | 92 | // Sets or clears checked status on menu item; This requires calling the original 93 | // "activate" class method on menu item 94 | pub(super) fn check_menu_item_set_checked(item: >k::CheckMenuItem, checked: bool) { 95 | if item.is_active() == checked { 96 | return; 97 | } 98 | activate_menu_item(item.upcast_ref::()); 99 | } 100 | 101 | pub(super) fn radio_menu_item_set_checked(item: >k::RadioMenuItem, checked: bool) { 102 | if item.is_active() == checked { 103 | return; 104 | } 105 | activate_menu_item(item.upcast_ref::()); 106 | } 107 | 108 | fn activate_menu_item(item: >k::MenuItem) { 109 | unsafe { 110 | let instance: *mut gtk_sys::GtkMenuItem = item.to_glib_none().0; 111 | 112 | let item_class = gobject_sys::g_type_class_peek(gtk_sys::gtk_check_menu_item_get_type()) 113 | as *mut gtk_sys::GtkMenuItemClass; 114 | let item_class = &*item_class; 115 | 116 | if let Some(activate) = item_class.activate { 117 | activate(instance as *mut _); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/src/hot_key.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | import 'accelerator.dart'; 5 | import 'api_constants.dart'; 6 | import 'keyboard_map.dart'; 7 | import 'mutex.dart'; 8 | 9 | // Global HotKey 10 | // Registered callback will be invoked when accelerator is pressed regardless 11 | // of whether application has keyboard focus or not. 12 | // Supported on macOS and Windows. 13 | class HotKey { 14 | HotKey._({ 15 | required _HotKeyHandle handle, 16 | required this.accelerator, 17 | required this.callback, 18 | }) : _handle = handle; 19 | 20 | _HotKeyHandle _handle; 21 | final Accelerator accelerator; 22 | final VoidCallback callback; 23 | 24 | static Future create({ 25 | required Accelerator accelerator, 26 | required VoidCallback callback, 27 | }) { 28 | return _HotKeyManager.instance 29 | .createHotKey(accelerator: accelerator, callback: callback); 30 | } 31 | 32 | Future dispose() async { 33 | _checkDisposed(); 34 | _disposed = true; 35 | await _HotKeyManager.instance.destroyHotKey(this); 36 | } 37 | 38 | void _checkDisposed() { 39 | assert(!_disposed, 'HotKey is already disposed.'); 40 | } 41 | 42 | bool _disposed = false; 43 | } 44 | 45 | class _HotKeyHandle { 46 | const _HotKeyHandle(this.value); 47 | 48 | final int value; 49 | 50 | @override 51 | bool operator ==(Object other) => 52 | identical(this, other) || 53 | (other is _HotKeyHandle && other.value == value); 54 | 55 | @override 56 | int get hashCode => value.hashCode; 57 | 58 | @override 59 | String toString() => 'HotKeyHandle($value)'; 60 | } 61 | 62 | // 63 | // 64 | // 65 | 66 | final _hotKeyChannel = MethodChannel(Channels.hotKeyManager); 67 | 68 | class _HotKeyManager { 69 | static final instance = _HotKeyManager(); 70 | final mutex = Mutex(); 71 | 72 | _HotKeyManager() { 73 | _hotKeyChannel.setMethodCallHandler(_onMethodCall); 74 | KeyboardMap.onChange.addListener(_keyboardLayoutChanged); 75 | } 76 | 77 | final _hotKeys = <_HotKeyHandle, HotKey>{}; 78 | 79 | Future _invoke(String method, dynamic arg) { 80 | return _hotKeyChannel.invokeMethod(method, arg); 81 | } 82 | 83 | Future _onMethodCall(MethodCall call) async { 84 | if (call.method == Methods.hotKeyOnPressed) { 85 | await mutex.protect(() async { 86 | final handle = _HotKeyHandle(call.arguments['handle'] as int); 87 | final key = _hotKeys[handle]; 88 | if (key != null) { 89 | key.callback(); 90 | } 91 | }); 92 | } 93 | } 94 | 95 | Future<_HotKeyHandle> _registerHotKeyLocked(Accelerator accelerator) async { 96 | return _HotKeyHandle(await _invoke(Methods.hotKeyCreate, { 97 | 'accelerator': accelerator.serialize(), 98 | 'platformKey': KeyboardMap.current().getPlatformKeyCode(accelerator.key!), 99 | })); 100 | } 101 | 102 | Future _createHotKeyLocked({ 103 | required Accelerator accelerator, 104 | required VoidCallback callback, 105 | }) async { 106 | final handle = await _registerHotKeyLocked(accelerator); 107 | final res = 108 | HotKey._(handle: handle, accelerator: accelerator, callback: callback); 109 | _hotKeys[res._handle] = res; 110 | return res; 111 | } 112 | 113 | Future createHotKey({ 114 | required Accelerator accelerator, 115 | required VoidCallback callback, 116 | }) async { 117 | return mutex.protect(() => 118 | _createHotKeyLocked(accelerator: accelerator, callback: callback)); 119 | } 120 | 121 | Future _destroyHotKeyLocked(_HotKeyHandle handle) async { 122 | await _invoke(Methods.hotKeyDestroy, {'handle': handle.value}); 123 | } 124 | 125 | Future destroyHotKey(HotKey hotKey) async { 126 | return mutex.protect(() => _destroyHotKeyLocked(hotKey._handle)); 127 | } 128 | 129 | // Hot-keys are registered on for virtual keys; Re-register hot keys if 130 | // keyboard layout changed. 131 | void _keyboardLayoutChanged() { 132 | mutex.protect(() async { 133 | final oldKeys = _hotKeys.keys.toList(growable: false); 134 | for (final key in oldKeys) { 135 | final hotKey = _hotKeys.remove(key); 136 | if (hotKey != null) { 137 | await _destroyHotKeyLocked(hotKey._handle); 138 | hotKey._handle = await _registerHotKeyLocked(hotKey.accelerator); 139 | _hotKeys[hotKey._handle] = hotKey; 140 | } 141 | } 142 | }); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /nativeshell/src/shell/platform/macos/binary_messenger.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, collections::HashSet, rc::Rc}; 2 | 3 | use super::error::PlatformResult; 4 | use crate::shell::BinaryMessengerReply; 5 | use block::{Block, ConcreteBlock, RcBlock}; 6 | use cocoa::{ 7 | base::{id, nil}, 8 | foundation::NSString, 9 | }; 10 | use objc::{class, msg_send, rc::StrongPtr, sel, sel_impl}; 11 | 12 | pub struct PlatformBinaryMessenger { 13 | handle: StrongPtr, 14 | registered: RefCell>, 15 | } 16 | 17 | impl PlatformBinaryMessenger { 18 | pub fn from_handle(handle: StrongPtr) -> Self { 19 | PlatformBinaryMessenger { 20 | handle, 21 | registered: RefCell::new(HashSet::new()), 22 | } 23 | } 24 | 25 | pub fn register_channel_handler(&self, channel: &str, callback: F) 26 | where 27 | F: Fn(&[u8], BinaryMessengerReply) + 'static, 28 | { 29 | unsafe { 30 | let closure = move |data: id, reply: &mut Block<(id,), ()>| { 31 | let bytes: *const u8 = msg_send![data, bytes]; 32 | let length: usize = msg_send![data, length]; 33 | let data: &[u8] = std::slice::from_raw_parts(bytes, length); 34 | 35 | let reply_block = RcBlock::copy(reply as *mut _); 36 | let cb = move |reply: &[u8]| { 37 | let data: id = 38 | msg_send![class!(NSData), dataWithBytes:reply.as_ptr() length:reply.len()]; 39 | reply_block.call((data,)); 40 | }; 41 | 42 | callback(data, BinaryMessengerReply::new(cb)); 43 | }; 44 | 45 | let block = ConcreteBlock::new(closure); 46 | let block = block.copy(); 47 | 48 | let channel = StrongPtr::new(NSString::alloc(nil).init_str(channel)); 49 | 50 | // macos embedder doesn't return anything useful here; It also leaks the block sadly 51 | let () = msg_send![*self.handle, setMessageHandlerOnChannel:*channel binaryMessageHandler:&*block]; 52 | } 53 | 54 | self.registered.borrow_mut().insert(channel.into()); 55 | } 56 | 57 | pub fn unregister_channel_handler(&self, channel: &str) { 58 | self.registered.borrow_mut().remove(channel); 59 | unsafe { 60 | let channel = StrongPtr::new(NSString::alloc(nil).init_str(channel)); 61 | let () = msg_send![*self.handle, setMessageHandlerOnChannel:*channel binaryMessageHandler:nil]; 62 | } 63 | } 64 | 65 | pub fn send_message(&self, channel: &str, message: &[u8], reply: F) -> PlatformResult<()> 66 | where 67 | F: FnOnce(&[u8]) + 'static, 68 | { 69 | // we know we're going to be called only once, but rust doesn't 70 | let r = Rc::new(RefCell::new(Some(reply))); 71 | unsafe { 72 | let closure = move |data: id| { 73 | let bytes: *const u8 = msg_send![data, bytes]; 74 | let length: usize = msg_send![data, length]; 75 | let data: &[u8] = std::slice::from_raw_parts(bytes, length); 76 | let reply = r.clone(); 77 | let function = reply.borrow_mut().take().unwrap(); 78 | function(data); 79 | }; 80 | let block = ConcreteBlock::new(closure); 81 | let block = block.copy(); 82 | let channel = StrongPtr::new(NSString::alloc(nil).init_str(channel)); 83 | let data: id = 84 | msg_send![class!(NSData), dataWithBytes:message.as_ptr() length:message.len()]; 85 | 86 | // sendOnChannel: doesn't have return value :-/ 87 | let () = 88 | msg_send![*self.handle, sendOnChannel:*channel message:data binaryReply:&*block]; 89 | } 90 | 91 | Ok(()) 92 | } 93 | 94 | pub fn post_message(&self, channel: &str, message: &[u8]) -> PlatformResult<()> { 95 | unsafe { 96 | let channel = StrongPtr::new(NSString::alloc(nil).init_str(channel)); 97 | let data: id = 98 | msg_send![class!(NSData), dataWithBytes:message.as_ptr() length:message.len()]; 99 | 100 | // sendOnChannel: doesn't have return value :-/ 101 | let () = msg_send![*self.handle, sendOnChannel:*channel message:data]; 102 | } 103 | 104 | Ok(()) 105 | } 106 | } 107 | 108 | impl Drop for PlatformBinaryMessenger { 109 | fn drop(&mut self) { 110 | let channels = self.registered.borrow().clone(); 111 | for c in channels.iter() { 112 | self.unregister_channel_handler(c); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /nativeshell/src/shell/method_call_handler.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::{Ref, RefCell, RefMut}, 3 | rc::{Rc, Weak}, 4 | }; 5 | 6 | use crate::codec::{MethodCall, MethodCallReply, MethodInvoker, StandardMethodCodec, Value}; 7 | 8 | use super::{Context, EngineHandle, Handle}; 9 | 10 | #[derive(Clone)] 11 | pub struct MethodInvokerProvider { 12 | context: Context, 13 | channel: String, 14 | } 15 | 16 | impl MethodInvokerProvider { 17 | pub fn get_method_invoker_for_engine(&self, handle: EngineHandle) -> MethodInvoker { 18 | MethodInvoker::new( 19 | self.context.clone(), 20 | handle, 21 | self.channel.clone(), 22 | &StandardMethodCodec, 23 | ) 24 | } 25 | } 26 | 27 | pub trait MethodCallHandler: Sized + 'static { 28 | fn on_method_call( 29 | &mut self, 30 | call: MethodCall, 31 | reply: MethodCallReply, 32 | engine: EngineHandle, 33 | ); 34 | 35 | // Implementation can store weak reference if it needs to pass it around. 36 | // Guaranteed to call before any other methods. 37 | fn assign_weak_self(&mut self, _weak_self: Weak>) {} 38 | 39 | // Keep the method invoker provider if you want to call methods on engines. 40 | fn assign_invoker_provider(&mut self, _provider: MethodInvokerProvider) {} 41 | 42 | // Called when engine is about to be destroyed. 43 | fn on_engine_destroyed(&mut self, _engine: EngineHandle) {} 44 | 45 | // Registers itself for handling platform channel methods. 46 | fn register(self, context: Context, channel: &str) -> RegisteredMethodCallHandler { 47 | RegisteredMethodCallHandler::new(context, channel, self) 48 | } 49 | } 50 | 51 | pub struct RegisteredMethodCallHandler { 52 | context: Context, 53 | channel: String, 54 | _destroy_engine_handle: Handle, 55 | handler: Rc>, 56 | } 57 | 58 | // Active method call handler 59 | impl RegisteredMethodCallHandler { 60 | fn new(context: Context, channel: &str, handler: T) -> Self { 61 | Self::new_ref(context, channel, Rc::new(RefCell::new(handler))) 62 | } 63 | 64 | fn new_ref(context: Context, channel: &str, handler: Rc>) -> Self { 65 | let context_ref = context.get().unwrap(); 66 | 67 | handler 68 | .borrow_mut() 69 | .assign_weak_self(Rc::downgrade(&handler)); 70 | 71 | let handler_clone = handler.clone(); 72 | let destroy_engine_handle = context_ref 73 | .engine_manager 74 | .borrow_mut() 75 | .register_destroy_engine_notification(move |handle| { 76 | handler_clone.borrow_mut().on_engine_destroyed(handle); 77 | }); 78 | 79 | handler 80 | .borrow_mut() 81 | .assign_invoker_provider(MethodInvokerProvider { 82 | context: context.clone(), 83 | channel: channel.into(), 84 | }); 85 | 86 | let handler_clone = handler.clone(); 87 | context_ref 88 | .message_manager 89 | .borrow_mut() 90 | .register_method_handler(channel, move |call, reply, engine| { 91 | handler_clone 92 | .borrow_mut() 93 | .on_method_call(call, reply, engine); 94 | }); 95 | 96 | Self { 97 | context, 98 | channel: channel.into(), 99 | _destroy_engine_handle: destroy_engine_handle, 100 | handler, 101 | } 102 | } 103 | 104 | pub fn borrow(&self) -> Ref { 105 | self.handler.borrow() 106 | } 107 | 108 | pub fn borrow_mut(&self) -> RefMut { 109 | self.handler.borrow_mut() 110 | } 111 | } 112 | 113 | impl Drop for RegisteredMethodCallHandler { 114 | fn drop(&mut self) { 115 | if let Some(context) = self.context.get() { 116 | context 117 | .message_manager 118 | .borrow_mut() 119 | .unregister_method_handler(&self.channel); 120 | } 121 | } 122 | } 123 | 124 | trait Holder {} 125 | 126 | impl Holder for RegisteredMethodCallHandler {} 127 | 128 | // Convenience interface for registering custom method call handlers 129 | pub struct MethodChannel { 130 | _registration: Box, 131 | } 132 | 133 | impl MethodChannel { 134 | pub fn new(context: Context, channel: &str, handler: H) -> Self 135 | where 136 | H: MethodCallHandler, 137 | { 138 | Self { 139 | _registration: Box::new(handler.register(context, channel)), 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /nativeshell/src/shell/event_channel.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::{Ref, RefCell, RefMut}, 3 | collections::HashMap, 4 | rc::{Rc, Weak}, 5 | }; 6 | 7 | use crate::{codec::Value, Error, Result}; 8 | 9 | use super::{Context, EngineHandle, MethodCallHandler, RegisteredMethodCallHandler}; 10 | 11 | pub struct EventSink { 12 | context: Context, 13 | id: i64, 14 | channel_name: String, 15 | engine_handle: EngineHandle, 16 | } 17 | 18 | impl EventSink { 19 | pub fn id(&self) -> i64 { 20 | self.id 21 | } 22 | 23 | pub fn send_message(&self, message: &Value) -> Result<()> { 24 | if let Some(context) = self.context.get() { 25 | context 26 | .message_manager 27 | .borrow() 28 | .get_event_sender(self.engine_handle, &self.channel_name) 29 | .send_event(message) 30 | } else { 31 | Err(Error::InvalidContext) 32 | } 33 | } 34 | } 35 | 36 | pub trait EventChannelHandler: Sized + 'static { 37 | // Implementation can store weak reference if it needs to pass it around. 38 | // Guaranteed to call before any other methods. 39 | fn assign_weak_self(&mut self, _weak_self: Weak>) {} 40 | 41 | // Implementation can store the event sink and use it to send event messages. 42 | fn register_event_sink(&mut self, sink: EventSink, listen_argument: Value); 43 | 44 | // Called when event sink has either been unregistered or engine stopped. 45 | fn unregister_event_sink(&mut self, sink_id: i64); 46 | 47 | // Registers itself for handling even sink registration methods. 48 | fn register(self, context: Context, channel: &str) -> RegisteredEventChannel { 49 | RegisteredEventChannel::new(context, channel, self) 50 | } 51 | } 52 | 53 | pub struct RegisteredEventChannel { 54 | _internal: RegisteredMethodCallHandler>, 55 | handler: Rc>, 56 | } 57 | 58 | impl RegisteredEventChannel { 59 | pub fn new(context: Context, channel: &str, handler: T) -> Self { 60 | Self::new_ref(context, channel, Rc::new(RefCell::new(handler))) 61 | } 62 | 63 | pub fn new_ref(context: Context, channel: &str, handler: Rc>) -> Self { 64 | handler 65 | .borrow_mut() 66 | .assign_weak_self(Rc::downgrade(&handler)); 67 | 68 | Self { 69 | _internal: EventChannelInternal { 70 | context: context.clone(), 71 | handler: handler.clone(), 72 | channel_name: channel.into(), 73 | next_sink_id: 1, 74 | engine_to_sink: HashMap::new(), 75 | } 76 | .register(context, channel), 77 | handler, 78 | } 79 | } 80 | 81 | pub fn borrow(&self) -> Ref { 82 | self.handler.borrow() 83 | } 84 | 85 | pub fn borrow_mut(&self) -> RefMut { 86 | self.handler.borrow_mut() 87 | } 88 | } 89 | 90 | // 91 | // Internal 92 | // 93 | 94 | struct EventChannelInternal { 95 | context: Context, 96 | channel_name: String, 97 | pub handler: Rc>, 98 | next_sink_id: i64, 99 | engine_to_sink: HashMap, 100 | } 101 | 102 | impl MethodCallHandler for EventChannelInternal { 103 | fn on_method_call( 104 | &mut self, 105 | call: crate::codec::MethodCall, 106 | reply: crate::codec::MethodCallReply, 107 | engine: super::EngineHandle, 108 | ) { 109 | match call.method.as_str() { 110 | "listen" => { 111 | let sink_id = self.next_sink_id; 112 | self.next_sink_id += 1; 113 | let sink = EventSink { 114 | context: self.context.clone(), 115 | id: sink_id, 116 | channel_name: self.channel_name.clone(), 117 | engine_handle: engine, 118 | }; 119 | self.handler 120 | .borrow_mut() 121 | .register_event_sink(sink, call.args); 122 | reply.send_ok(Value::Null); 123 | } 124 | "cancel" => { 125 | if let Some(sink_id) = self.engine_to_sink.remove(&engine) { 126 | self.handler.borrow_mut().unregister_event_sink(sink_id); 127 | } 128 | reply.send_ok(Value::Null); 129 | } 130 | _ => {} 131 | } 132 | } 133 | 134 | // Called when engine is about to be destroyed. 135 | fn on_engine_destroyed(&mut self, engine: EngineHandle) { 136 | if let Some(sink_id) = self.engine_to_sink.remove(&engine) { 137 | self.handler.borrow_mut().unregister_event_sink(sink_id); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /nativeshell_build/src/plugins_linux.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::{self, File}, 3 | io::Write, 4 | path::Path, 5 | }; 6 | 7 | use cmake::Config; 8 | use convert_case::{Case, Casing}; 9 | 10 | use crate::{ 11 | artifacts_emitter::ArtifactsEmitter, 12 | util::{copy, copy_to, get_artifacts_dir, mkdir}, 13 | BuildResult, FileOperation, Flutter, IOResultExt, 14 | }; 15 | 16 | use super::Plugin; 17 | 18 | pub(super) struct PluginsImpl<'a> { 19 | build: &'a Flutter<'a>, 20 | artifacts_emitter: &'a ArtifactsEmitter<'a>, 21 | } 22 | 23 | impl<'a> PluginsImpl<'a> { 24 | pub fn new(build: &'a Flutter, artifacts_emitter: &'a ArtifactsEmitter<'a>) -> Self { 25 | Self { 26 | build, 27 | artifacts_emitter, 28 | } 29 | } 30 | 31 | pub fn process(&self, plugins: &[Plugin], skip_build: bool) -> BuildResult<()> { 32 | let flutter_artifacts = self 33 | .artifacts_emitter 34 | .find_artifacts_location(&self.build.build_mode)?; 35 | 36 | let plugins_dir = mkdir(&self.build.out_dir, Some("plugins"))?; 37 | let flutter_files = ["flutter_linux", "libflutter_linux_gtk.so"]; 38 | for file in &flutter_files { 39 | copy_to(&flutter_artifacts.join(file), &plugins_dir, true)?; 40 | } 41 | 42 | let flutter = mkdir(&plugins_dir, Some("flutter"))?; 43 | for plugin in plugins { 44 | copy(&plugin.platform_path, flutter.join(&plugin.name), true)?; 45 | } 46 | 47 | let mut cmakelist: String = include_str!("res/linux/CMakeLists.txt").into(); 48 | for plugin in plugins { 49 | cmakelist.push_str(&format!("add_subdirectory(\"flutter/{}\")\n", plugin.name)); 50 | } 51 | 52 | let cmakelist_path = plugins_dir.join("CMakeLists.txt"); 53 | std::fs::write(&cmakelist_path, &cmakelist) 54 | .wrap_error(FileOperation::Write, || cmakelist_path)?; 55 | 56 | if !skip_build { 57 | Config::new(plugins_dir).no_build_target(true).build(); 58 | } 59 | 60 | let artifacts_dir = mkdir(get_artifacts_dir()?, Some("lib"))?; 61 | 62 | for plugin in plugins { 63 | let plugin_artifacts_path = self 64 | .build 65 | .out_dir 66 | .join("build") 67 | .join("flutter") 68 | .join(&plugin.name); 69 | 70 | for entry in fs::read_dir(&plugin_artifacts_path) 71 | .wrap_error(FileOperation::ReadDir, || plugin_artifacts_path.clone())? 72 | { 73 | let entry = 74 | entry.wrap_error(FileOperation::ReadDir, || plugin_artifacts_path.clone())?; 75 | 76 | let name = entry.file_name(); 77 | let name = name.to_string_lossy(); 78 | if let Some(name) = name.strip_suffix(".so") { 79 | copy_to(entry.path(), &artifacts_dir, true)?; 80 | let name = name.strip_prefix("lib").unwrap(); 81 | cargo_emit::rustc_link_lib! { 82 | name 83 | }; 84 | } 85 | } 86 | } 87 | 88 | self.write_plugin_registrar(plugins)?; 89 | 90 | Ok(()) 91 | } 92 | 93 | fn write_plugin_registrar(&self, plugins: &[Plugin]) -> BuildResult<()> { 94 | let path = self.build.out_dir.join("generated_plugins_registrar.rs"); 95 | self._write_plugin_registrar(&path, plugins) 96 | .wrap_error(FileOperation::Write, || path) 97 | } 98 | 99 | fn _write_plugin_registrar(&self, path: &Path, plugins: &[Plugin]) -> std::io::Result<()> { 100 | let mut file = File::create(path)?; 101 | 102 | writeln!( 103 | file, 104 | "fn flutter_get_plugins() -> Vec {{" 105 | )?; 106 | 107 | for plugin in plugins { 108 | let snake_case_class = plugin 109 | .platform_info 110 | .plugin_class 111 | .from_case(Case::Pascal) 112 | .to_case(Case::Snake); 113 | writeln!(file, " extern \"C\" {{ pub fn {}_register_with_registrar(registrar: *mut std::os::raw::c_void); }}", snake_case_class)?; 114 | } 115 | 116 | writeln!(file, " vec![")?; 117 | for plugin in plugins { 118 | let class = &plugin.platform_info.plugin_class; 119 | let snake_case_class = class.from_case(Case::Pascal).to_case(Case::Snake); 120 | writeln!( 121 | file, 122 | " nativeshell::shell::platform::engine::PlatformPlugin {{ \ 123 | name: \"{}\".into(), register_func: Some({}_register_with_registrar) }},", 124 | class, snake_case_class, 125 | )?; 126 | } 127 | 128 | writeln!(file, " ]")?; 129 | writeln!(file, "}}")?; 130 | 131 | Ok(()) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/src/menu.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'accelerator.dart'; 4 | import 'menu_internal.dart'; 5 | 6 | enum MenuItemRole { 7 | // macOS specific 8 | about, 9 | 10 | // macOS specific 11 | hide, 12 | 13 | // macOS specific 14 | hideOtherApplications, 15 | 16 | // macOS specific 17 | showAll, 18 | 19 | // macOS specific 20 | quitApplication, 21 | 22 | // macOS specific 23 | minimizeWindow, 24 | 25 | // macOS specific 26 | zoomWindow, 27 | 28 | // macOS specific 29 | bringAllToFront, 30 | } 31 | 32 | enum MenuRole { 33 | // macOS specific; Menus marked with window will have additional Window specific items in it 34 | window, 35 | 36 | // macOS specific; Services menu 37 | services, 38 | } 39 | 40 | enum CheckStatus { 41 | none, 42 | checkOn, 43 | checkOff, 44 | radioOn, 45 | radioOff, 46 | } 47 | 48 | class MenuItem { 49 | MenuItem({ 50 | required this.title, 51 | required this.action, 52 | this.checkStatus = CheckStatus.none, 53 | this.accelerator, 54 | }) : separator = false, 55 | submenu = null, 56 | role = null; 57 | 58 | MenuItem.menu({ 59 | required this.title, 60 | required this.submenu, 61 | }) : separator = false, 62 | action = null, 63 | checkStatus = CheckStatus.none, 64 | role = null, 65 | accelerator = null; 66 | 67 | MenuItem.children({ 68 | required String title, 69 | required List children, 70 | MenuRole? role, 71 | }) : this.builder( 72 | title: title, 73 | builder: () => children, 74 | role: role, 75 | ); 76 | 77 | MenuItem.builder({ 78 | required String title, 79 | required MenuBuilder builder, 80 | MenuRole? role, 81 | }) : this.menu( 82 | title: title, 83 | submenu: Menu(builder, role: role), 84 | ); 85 | 86 | MenuItem.withRole({ 87 | required MenuItemRole role, 88 | String? title, 89 | this.accelerator, 90 | }) : action = null, 91 | separator = false, 92 | checkStatus = CheckStatus.none, 93 | title = title ?? _titleForRole(role), 94 | role = role, 95 | submenu = null; 96 | 97 | MenuItem.separator() 98 | : title = '', 99 | action = null, 100 | separator = true, 101 | checkStatus = CheckStatus.none, 102 | role = null, 103 | submenu = null, 104 | accelerator = null; 105 | 106 | final String title; 107 | final MenuItemRole? role; 108 | 109 | final VoidCallback? action; 110 | 111 | final Menu? submenu; 112 | 113 | final bool separator; 114 | final CheckStatus checkStatus; 115 | 116 | bool get disabled => submenu == null && action == null; 117 | 118 | final Accelerator? accelerator; 119 | 120 | @override 121 | bool operator ==(dynamic other) => 122 | identical(this, other) || 123 | (other is MenuItem && separator && other.separator) || 124 | (other is MenuItem && 125 | title == other.title && 126 | (submenu == null) == (other.submenu == null) && 127 | role == other.role && 128 | checkStatus == other.checkStatus && 129 | accelerator == other.accelerator && 130 | (action == null) == (other.action == null)); 131 | 132 | @override 133 | int get hashCode => hashValues(title, separator, submenu != null); 134 | 135 | static String _titleForRole(MenuItemRole role) { 136 | switch (role) { 137 | case MenuItemRole.about: 138 | return 'About'; 139 | case MenuItemRole.hide: 140 | return 'Hide'; 141 | case MenuItemRole.hideOtherApplications: 142 | return 'Hide Others'; 143 | case MenuItemRole.showAll: 144 | return 'Show All'; 145 | case MenuItemRole.quitApplication: 146 | return 'Quit'; 147 | case MenuItemRole.minimizeWindow: 148 | return 'Minimize'; 149 | case MenuItemRole.zoomWindow: 150 | return 'Zoom'; 151 | case MenuItemRole.bringAllToFront: 152 | return 'Bring All to Front'; 153 | } 154 | } 155 | } 156 | 157 | typedef MenuBuilder = List Function(); 158 | 159 | class Menu { 160 | Menu( 161 | this.builder, { 162 | this.role, 163 | this.onOpen, 164 | }) { 165 | state = MenuState(this); 166 | } 167 | 168 | final MenuBuilder builder; 169 | final MenuRole? role; 170 | final VoidCallback? onOpen; 171 | 172 | // Internal state of the menu 173 | late final MenuState state; 174 | 175 | void update() { 176 | state.update(); 177 | } 178 | 179 | // macOS specific. Sets this menu as application menu. It will be shown 180 | // for every window that doesn't have window specific menu. 181 | Future setAsAppMenu() { 182 | return state.setAsAppMenu(); 183 | } 184 | } 185 | 186 | class MenuHandle { 187 | const MenuHandle(this.value); 188 | 189 | final int value; 190 | 191 | @override 192 | bool operator ==(Object other) => 193 | identical(this, other) || (other is MenuHandle && other.value == value); 194 | 195 | @override 196 | int get hashCode => value.hashCode; 197 | 198 | @override 199 | String toString() => 'MenuHandle($value)'; 200 | } 201 | -------------------------------------------------------------------------------- /nativeshell_build/src/plugins_windows.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::{self, File}, 3 | io::Write, 4 | path::Path, 5 | }; 6 | 7 | use cmake::Config; 8 | 9 | use crate::{ 10 | artifacts_emitter::ArtifactsEmitter, 11 | util::{copy, copy_to, get_artifacts_dir, mkdir}, 12 | BuildResult, FileOperation, Flutter, IOResultExt, 13 | }; 14 | 15 | use super::Plugin; 16 | 17 | pub(super) struct PluginsImpl<'a> { 18 | build: &'a Flutter<'a>, 19 | artifacts_emitter: &'a ArtifactsEmitter<'a>, 20 | } 21 | 22 | impl<'a> PluginsImpl<'a> { 23 | pub fn new(build: &'a Flutter, artifacts_emitter: &'a ArtifactsEmitter<'a>) -> Self { 24 | Self { 25 | build, 26 | artifacts_emitter, 27 | } 28 | } 29 | 30 | pub fn process(&self, plugins: &[Plugin], skip_build: bool) -> BuildResult<()> { 31 | let flutter_artifacts = self 32 | .artifacts_emitter 33 | .find_artifacts_location(&self.build.build_mode)?; 34 | 35 | let plugins_dir = mkdir(&self.build.out_dir, Some("plugins"))?; 36 | let flutter_files = [ 37 | "flutter_export.h", 38 | "flutter_messenger.h", 39 | "flutter_plugin_registrar.h", 40 | "flutter_texture_registrar.h", 41 | "flutter_windows.h", 42 | "flutter_windows.dll", 43 | "flutter_windows.dll.lib", 44 | ]; 45 | for file in &flutter_files { 46 | copy_to(&flutter_artifacts.join(file), &plugins_dir, true)?; 47 | } 48 | 49 | let cpp_client_wrapper = self 50 | .artifacts_emitter 51 | .find_artifacts_location("debug")? 52 | .join("cpp_client_wrapper"); 53 | copy_to(&cpp_client_wrapper, &plugins_dir, true)?; 54 | 55 | let flutter = mkdir(&plugins_dir, Some("flutter"))?; 56 | for plugin in plugins { 57 | copy(&plugin.platform_path, flutter.join(&plugin.name), true)?; 58 | } 59 | 60 | let mut cmakelist: String = include_str!("res/windows/CMakeLists.txt").into(); 61 | for plugin in plugins { 62 | cmakelist.push_str(&format!("add_subdirectory(\"flutter/{}\")\n", plugin.name)); 63 | } 64 | 65 | let cmakelist_path = plugins_dir.join("CMakeLists.txt"); 66 | std::fs::write(&cmakelist_path, &cmakelist) 67 | .wrap_error(FileOperation::Write, || cmakelist_path)?; 68 | 69 | let mut cmake = Config::new(plugins_dir); 70 | if !skip_build { 71 | cmake.no_build_target(true).build(); 72 | } 73 | 74 | let configuration = cmake.get_profile(); 75 | 76 | let artifacts_dir = get_artifacts_dir()?; 77 | 78 | for plugin in plugins { 79 | let plugin_artifacts_path = self 80 | .build 81 | .out_dir 82 | .join("build") 83 | .join("flutter") 84 | .join(&plugin.name) 85 | .join(&configuration); 86 | 87 | for entry in fs::read_dir(&plugin_artifacts_path) 88 | .wrap_error(FileOperation::ReadDir, || plugin_artifacts_path.clone())? 89 | { 90 | let entry = 91 | entry.wrap_error(FileOperation::ReadDir, || plugin_artifacts_path.clone())?; 92 | 93 | let name = entry.file_name(); 94 | let name = name.to_string_lossy(); 95 | if name.ends_with(".dll") || name.ends_with(".lib") { 96 | copy_to(entry.path(), &artifacts_dir, true)?; 97 | } 98 | if let Some(name) = name.strip_suffix(".dll") { 99 | cargo_emit::rustc_link_lib! { 100 | name 101 | }; 102 | } 103 | } 104 | } 105 | 106 | self.write_plugin_registrar(plugins)?; 107 | 108 | Ok(()) 109 | } 110 | 111 | fn write_plugin_registrar(&self, plugins: &[Plugin]) -> BuildResult<()> { 112 | let path = self.build.out_dir.join("generated_plugins_registrar.rs"); 113 | self._write_plugin_registrar(&path, plugins) 114 | .wrap_error(FileOperation::Write, || path) 115 | } 116 | 117 | fn _write_plugin_registrar(&self, path: &Path, plugins: &[Plugin]) -> std::io::Result<()> { 118 | let mut file = File::create(path)?; 119 | 120 | writeln!( 121 | file, 122 | "fn flutter_get_plugins() -> Vec {{" 123 | )?; 124 | 125 | for plugin in plugins { 126 | writeln!(file, " extern \"C\" {{ pub fn {}RegisterWithRegistrar(registrar: *mut std::os::raw::c_void); }}", plugin.platform_info.plugin_class)?; 127 | } 128 | 129 | writeln!(file, " vec![")?; 130 | for plugin in plugins { 131 | let class = &plugin.platform_info.plugin_class; 132 | writeln!( 133 | file, 134 | " nativeshell::shell::platform::engine::PlatformPlugin {{ \ 135 | name: \"{}\".into(), register_func: Some({}RegisterWithRegistrar) }},", 136 | class, class, 137 | )?; 138 | } 139 | 140 | writeln!(file, " ]")?; 141 | writeln!(file, "}}")?; 142 | 143 | Ok(()) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /nativeshell_dart/lib/src/accelerator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'key_interceptor.dart'; 4 | import 'keyboard_map.dart'; 5 | import 'menu.dart'; 6 | 7 | // Defines a keyboard shortcut. 8 | // Can be conveniently constructed: 9 | // 10 | // import 'package:nativeshell/accelerators.dart'; 11 | // final accelerator1 = cmdOrCtrl + shift + 'e'; 12 | // final accelerator2 = alt + f1; 13 | class Accelerator { 14 | const Accelerator({ 15 | this.key, 16 | this.alt = false, 17 | this.control = false, 18 | this.meta = false, 19 | this.shift = false, 20 | }); 21 | 22 | final KeyboardKey? key; 23 | final bool alt; 24 | final bool control; 25 | final bool meta; 26 | final bool shift; 27 | 28 | String get label { 29 | final k = key; 30 | if (k is LogicalKeyboardKey) { 31 | return k.keyLabel; 32 | } else if (k is PhysicalKeyboardKey) { 33 | // on macOS CMD (meta) on some keyboards (SVK) resulsts in US key code. 34 | // So we need to take that into account when generating labels 35 | // (used for key equivalents on NSMenuItem) otherwise the shortcut won't 36 | // be matched. 37 | final logical = 38 | KeyboardMap.current().getLogicalKeyForPhysicalKey(k, meta: meta); 39 | if (logical != null) { 40 | return logical.keyLabel; 41 | } 42 | } 43 | return '??'; 44 | } 45 | 46 | Accelerator operator +(dynamic that) { 47 | if (that is num) { 48 | that = '$that'; 49 | } 50 | 51 | if (that is KeyboardKey) { 52 | that = Accelerator(key: that); 53 | } 54 | 55 | if (that is String) { 56 | assert(that.codeUnits.length == 1); 57 | final lower = that.toLowerCase(); 58 | return this + 59 | Accelerator( 60 | key: _keyForCodeUnit(lower.codeUnits[0]), shift: lower != that); 61 | } else if (that is Accelerator) { 62 | return Accelerator( 63 | key: that.key ?? key, 64 | alt: alt || that.alt, 65 | shift: shift || that.shift, 66 | control: control || that.control, 67 | meta: meta || that.meta); 68 | } else { 69 | throw ArgumentError( 70 | 'Argument must be String, Accelerator or single digit number'); 71 | } 72 | } 73 | 74 | @override 75 | bool operator ==(dynamic other) => 76 | identical(this, other) || 77 | (other is Accelerator && 78 | alt == other.alt && 79 | control == other.control && 80 | meta == other.meta && 81 | shift == other.shift && 82 | key == other.key); 83 | 84 | @override 85 | int get hashCode => hashValues(alt, control, meta, shift, key); 86 | 87 | bool matches(RawKeyEvent event) { 88 | final key = this.key; 89 | if (key != null) { 90 | final physicalKey = key is PhysicalKeyboardKey 91 | ? key 92 | : KeyboardMap.current() 93 | .getPhysicalKeyForLogicalKey(key as LogicalKeyboardKey); 94 | return event.isAltPressed == alt && 95 | event.isControlPressed == control && 96 | event.isMetaPressed == meta && 97 | event.isShiftPressed == shift && 98 | physicalKey == event.physicalKey; 99 | } else { 100 | return false; 101 | } 102 | } 103 | 104 | LogicalKeyboardKey _keyForCodeUnit(int codeUnit) { 105 | final keyId = LogicalKeyboardKey.unicodePlane | 106 | (codeUnit & LogicalKeyboardKey.valueMask); 107 | return LogicalKeyboardKey.findKeyByKeyId(keyId) ?? 108 | LogicalKeyboardKey( 109 | keyId, 110 | ); 111 | } 112 | 113 | dynamic serialize() => key != null 114 | ? { 115 | 'label': label, 116 | 'alt': alt, 117 | 'shift': shift, 118 | 'meta': meta, 119 | 'control': control, 120 | } 121 | : null; 122 | } 123 | 124 | class AcceleratorRegistry { 125 | AcceleratorRegistry._() { 126 | KeyInterceptor.instance 127 | .registerHandler(_handleKeyEvent, stage: InterceptorStage.pre); 128 | } 129 | 130 | void register(Accelerator accelerator, VoidCallback callback) { 131 | _accelerators[accelerator] = callback; 132 | } 133 | 134 | void unregister(Accelerator accelerator) { 135 | _accelerators.remove(accelerator); 136 | } 137 | 138 | void registerMenu(Menu menu) { 139 | _menus.add(menu); 140 | } 141 | 142 | void unregisterMenu(Menu menu) { 143 | _menus.remove(menu); 144 | } 145 | 146 | bool _handleKeyEvent(RawKeyEvent event) { 147 | var handled = false; 148 | if (event is RawKeyDownEvent) { 149 | for (final a in _accelerators.entries) { 150 | if (a.key.matches(event)) { 151 | a.value(); 152 | handled = true; 153 | break; 154 | } 155 | } 156 | 157 | if (!handled) { 158 | for (final m in _menus) { 159 | final action = m.state.actionForEvent(event); 160 | if (action != null) { 161 | action(); 162 | handled = true; 163 | break; 164 | } 165 | } 166 | } 167 | } 168 | 169 | return handled; 170 | } 171 | 172 | final _accelerators = {}; 173 | final _menus = []; 174 | } 175 | 176 | final accelerators = AcceleratorRegistry._(); 177 | -------------------------------------------------------------------------------- /nativeshell/src/shell/run_loop.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::{RefCell, UnsafeCell}, 3 | future::Future, 4 | marker::PhantomData, 5 | rc::Rc, 6 | sync::Arc, 7 | task::Poll, 8 | time::Duration, 9 | }; 10 | 11 | use futures::{ 12 | future::LocalBoxFuture, 13 | task::{waker_ref, ArcWake}, 14 | FutureExt, 15 | }; 16 | 17 | use super::{ 18 | platform::run_loop::{PlatformRunLoop, PlatformRunLoopSender}, 19 | Context, ContextRef, Handle, 20 | }; 21 | 22 | pub struct RunLoop { 23 | pub(super) platform_run_loop: Rc, 24 | context: Context, 25 | } 26 | 27 | impl RunLoop { 28 | pub fn new(context: &ContextRef) -> Self { 29 | Self { 30 | platform_run_loop: Rc::new(PlatformRunLoop::new()), 31 | context: context.weak(), 32 | } 33 | } 34 | 35 | #[must_use] 36 | pub fn schedule(&self, in_time: Duration, callback: F) -> Handle 37 | where 38 | F: FnOnce() + 'static, 39 | { 40 | let run_loop = self.platform_run_loop.clone(); 41 | let handle = run_loop.schedule(in_time, callback); 42 | Handle::new(move || { 43 | run_loop.unschedule(handle); 44 | }) 45 | } 46 | 47 | // Convenience method to schedule callback on next run loop turn 48 | #[must_use] 49 | pub fn schedule_now(&self, callback: F) -> Handle 50 | where 51 | F: FnOnce() + 'static, 52 | { 53 | self.schedule(Duration::from_secs(0), callback) 54 | } 55 | 56 | pub fn run(&self) { 57 | // set context as current 58 | let _handle = self.context.get().unwrap().set_as_current(); 59 | self.platform_run_loop.run() 60 | } 61 | 62 | pub fn stop(&self) { 63 | self.platform_run_loop.stop() 64 | } 65 | 66 | pub fn new_sender(&self) -> RunLoopSender { 67 | RunLoopSender { 68 | platform_sender: self.platform_run_loop.new_sender(), 69 | } 70 | } 71 | 72 | // Spawn the future with current run loop being the executor; 73 | pub fn spawn(&self, future: impl Future + 'static) -> JoinHandle { 74 | let future = future.boxed_local(); 75 | let task = Arc::new(Task { 76 | sender: self.new_sender(), 77 | future: UnsafeCell::new(future), 78 | value: RefCell::new(None), 79 | waker: RefCell::new(None), 80 | }); 81 | ArcWake::wake_by_ref(&task); 82 | JoinHandle { 83 | task, 84 | _data: PhantomData {}, 85 | } 86 | } 87 | } 88 | 89 | // Can be used to send callbacks from other threads to be executed on run loop thread 90 | #[derive(Clone)] 91 | pub struct RunLoopSender { 92 | platform_sender: PlatformRunLoopSender, 93 | } 94 | 95 | impl RunLoopSender { 96 | pub fn send(&self, callback: F) 97 | where 98 | F: FnOnce() + 'static + Send, 99 | { 100 | self.platform_sender.send(callback) 101 | } 102 | } 103 | 104 | // 105 | // 106 | // 107 | 108 | struct Task { 109 | sender: RunLoopSender, 110 | future: UnsafeCell>, 111 | value: RefCell>, 112 | waker: RefCell>, 113 | } 114 | 115 | // Tasks can only be spawned on run loop thread and will only be executed 116 | // on run loop thread. ArcWake however doesn't know this. 117 | unsafe impl Send for Task {} 118 | unsafe impl Sync for Task {} 119 | 120 | impl Task { 121 | fn poll(self: &std::sync::Arc) -> Poll { 122 | let waker = waker_ref(self).clone(); 123 | let context = &mut core::task::Context::from_waker(&waker); 124 | unsafe { 125 | let future = &mut *self.future.get(); 126 | future.as_mut().poll(context) 127 | } 128 | } 129 | } 130 | 131 | impl ArcWake for Task { 132 | fn wake_by_ref(arc_self: &std::sync::Arc) { 133 | let arc_self = arc_self.clone(); 134 | let sender = arc_self.sender.clone(); 135 | sender.send(move || { 136 | if arc_self.value.borrow().is_none() { 137 | if let Poll::Ready(value) = arc_self.poll() { 138 | *arc_self.value.borrow_mut() = Some(value); 139 | } 140 | } 141 | if arc_self.value.borrow().is_some() { 142 | if let Some(waker) = arc_self.waker.borrow_mut().take() { 143 | waker.wake(); 144 | } 145 | } 146 | }); 147 | } 148 | } 149 | 150 | pub struct JoinHandle { 151 | task: Arc>, 152 | // Task has unsafe `Send` and `Sync`, but that is only because we know 153 | // it will not be polled from another thread. This is to ensure that 154 | // JoinHandle is neither Send nor Sync. 155 | _data: PhantomData<*const ()>, 156 | } 157 | 158 | impl Future for JoinHandle { 159 | type Output = T; 160 | 161 | fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { 162 | let value = self.task.value.borrow_mut().take(); 163 | match value { 164 | Some(value) => Poll::Ready(value), 165 | None => { 166 | self.task 167 | .waker 168 | .borrow_mut() 169 | .get_or_insert_with(|| cx.waker().clone()); 170 | Poll::Pending 171 | } 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /nativeshell/src/shell/engine_manager.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::{Ref, RefCell}, 3 | collections::HashMap, 4 | }; 5 | 6 | use super::{Context, ContextRef, FlutterEngine, Handle}; 7 | use crate::{Error, Result}; 8 | 9 | #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] 10 | pub struct EngineHandle(pub i64); 11 | 12 | pub struct EngineManager { 13 | context: Context, 14 | engines: HashMap>>, 15 | next_handle: EngineHandle, 16 | next_notification: i64, 17 | create_notifications: HashMap>, 18 | destroy_notifications: HashMap>, 19 | } 20 | 21 | impl EngineManager { 22 | pub(super) fn new(context: &ContextRef) -> Self { 23 | Self { 24 | context: context.weak(), 25 | engines: HashMap::new(), 26 | next_handle: EngineHandle(1), 27 | next_notification: 1, 28 | create_notifications: HashMap::new(), 29 | destroy_notifications: HashMap::new(), 30 | } 31 | } 32 | 33 | pub fn create_engine(&mut self, parent_engine: Option) -> Result { 34 | if let Some(context) = self.context.get() { 35 | let engine = FlutterEngine::new(&context.options.flutter_plugins, parent_engine); 36 | let handle = self.next_handle; 37 | 38 | for n in self.create_notifications.values() { 39 | n(handle, &engine); 40 | } 41 | 42 | self.next_handle.0 += 1; 43 | self.engines.insert(handle, Box::new(RefCell::new(engine))); 44 | 45 | context 46 | .message_manager 47 | .borrow_mut() 48 | .engine_created(self, handle); 49 | Ok(handle) 50 | } else { 51 | Err(Error::InvalidContext) 52 | } 53 | } 54 | 55 | pub fn launch_engine(&mut self, handle: EngineHandle) -> Result<()> { 56 | self.engines 57 | .get(&handle) 58 | .map(|engine| engine.borrow_mut().launch()) 59 | .transpose()? 60 | .ok_or(Error::InvalidEngineHandle) 61 | } 62 | 63 | pub fn get_engine(&self, handle: EngineHandle) -> Option> { 64 | self.engines.get(&handle).map(|a| a.borrow()) 65 | } 66 | 67 | // Returns the handle of engine responsible for creating provided engine. It is valid 68 | // for this method to return handle to engine that is no longer active. 69 | pub fn get_parent_engine(&self, handle: EngineHandle) -> Option { 70 | self.engines 71 | .get(&handle) 72 | .and_then(|e| e.borrow().parent_engine) 73 | } 74 | 75 | #[must_use] 76 | pub fn register_create_engine_notification(&mut self, notification: F) -> Handle 77 | where 78 | F: Fn(EngineHandle, &FlutterEngine) + 'static, 79 | { 80 | let handle = self.next_notification; 81 | self.next_notification += 1; 82 | 83 | self.create_notifications 84 | .insert(handle, Box::new(notification)); 85 | 86 | let context = self.context.clone(); 87 | Handle::new(move || { 88 | if let Some(context) = context.get() { 89 | context 90 | .engine_manager 91 | .borrow_mut() 92 | .create_notifications 93 | .remove(&handle); 94 | } 95 | }) 96 | } 97 | 98 | #[must_use] 99 | pub fn register_destroy_engine_notification(&mut self, notification: F) -> Handle 100 | where 101 | F: Fn(EngineHandle) + 'static, 102 | { 103 | let handle = self.next_notification; 104 | self.next_notification += 1; 105 | 106 | self.destroy_notifications 107 | .insert(handle, Box::new(notification)); 108 | 109 | let context = self.context.clone(); 110 | Handle::new(move || { 111 | if let Some(context) = context.get() { 112 | context 113 | .engine_manager 114 | .borrow_mut() 115 | .destroy_notifications 116 | .remove(&handle); 117 | } 118 | }) 119 | } 120 | 121 | pub fn remove_engine(&mut self, handle: EngineHandle) -> Result<()> { 122 | for n in self.destroy_notifications.values() { 123 | n(handle); 124 | } 125 | 126 | let entry = self.engines.remove(&handle); 127 | if let Some(entry) = entry { 128 | let mut engine = entry.borrow_mut(); 129 | engine.shut_down()?; 130 | } 131 | if self.engines.is_empty() { 132 | if let Some(context) = self.context.get() { 133 | (context.options.on_last_engine_removed)(&context); 134 | } 135 | } 136 | Ok(()) 137 | } 138 | 139 | pub fn get_all_engines(&self) -> Vec { 140 | self.engines.keys().cloned().collect() 141 | } 142 | 143 | pub fn shut_down(&mut self) -> Result<()> { 144 | let engines = self.get_all_engines(); 145 | for engine in engines { 146 | self.remove_engine(engine)?; 147 | } 148 | Ok(()) 149 | } 150 | 151 | // Posts message on all engines 152 | pub fn broadcast_message(&self, channel: &str, message: &[u8]) -> Result<()> { 153 | for engine in self.engines.values() { 154 | engine 155 | .borrow() 156 | .binary_messenger() 157 | .post_message(channel, message)?; 158 | } 159 | Ok(()) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /nativeshell_build/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Display, io, path::PathBuf, process::ExitStatus}; 2 | 3 | #[derive(Debug)] 4 | pub enum FileOperation { 5 | CreateDir, 6 | Copy, 7 | Move, 8 | Remove, 9 | RemoveDir, 10 | Read, 11 | Write, 12 | Open, 13 | Create, 14 | SymLink, 15 | MetaData, 16 | CopyDir, 17 | MkDir, 18 | ReadDir, 19 | Canonicalize, 20 | Command, 21 | Unarchive, 22 | } 23 | #[derive(Debug)] 24 | pub enum BuildError { 25 | ToolError { 26 | command: String, 27 | status: ExitStatus, 28 | stderr: String, 29 | stdout: String, 30 | }, 31 | FlutterNotFoundError, 32 | FlutterPathInvalidError { 33 | path: PathBuf, 34 | }, 35 | FlutterLocalEngineNotFound, 36 | FileOperationError { 37 | operation: FileOperation, 38 | path: PathBuf, 39 | source_path: Option, 40 | source: io::Error, 41 | }, 42 | JsonError { 43 | text: Option, 44 | source: serde_json::Error, 45 | }, 46 | YamlError { 47 | source: yaml_rust::ScanError, 48 | }, 49 | OtherError(String), 50 | } 51 | 52 | pub type BuildResult = Result; 53 | 54 | impl Display for BuildError { 55 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 56 | match self { 57 | BuildError::ToolError { 58 | command, 59 | status, 60 | stderr, 61 | stdout, 62 | } => { 63 | write!( 64 | f, 65 | "External Tool Failed!\nStatus: {:?}\nCommand: {}\nStderr:\n{}\nStdout:\n{}", 66 | status, command, stderr, stdout 67 | ) 68 | } 69 | BuildError::FileOperationError { 70 | operation, 71 | path, 72 | source_path, 73 | source, 74 | } => match source_path { 75 | Some(source_path) => { 76 | write!( 77 | f, 78 | "File operation failed: {:?}, target path: {:?}, source path: {:?}, error: {}", 79 | operation, path, source_path, source 80 | ) 81 | } 82 | None => { 83 | write!( 84 | f, 85 | "File operation failed: {:?}, path: {:?}, error: {}", 86 | operation, path, source 87 | ) 88 | } 89 | }, 90 | BuildError::JsonError { text, source } => { 91 | write!(f, "JSON operation failed: ${}", source)?; 92 | if let Some(text) = text { 93 | write!(f, "Text:\n{}", text)?; 94 | } 95 | Ok(()) 96 | } 97 | BuildError::YamlError { source } => { 98 | write!(f, "{}", source) 99 | } 100 | BuildError::OtherError(err) => { 101 | write!(f, "{}", err) 102 | } 103 | BuildError::FlutterNotFoundError => { 104 | write!( 105 | f, 106 | "Couldn't find Flutter installation. \ 107 | Plase make sure 'flutter' executable is in PATH \ 108 | or specify 'flutter_path' in FlutterOptions" 109 | ) 110 | } 111 | BuildError::FlutterPathInvalidError { path } => { 112 | write!( 113 | f, 114 | "Flutter path {:?} does not point to a valid flutter installation", 115 | path 116 | ) 117 | } 118 | BuildError::FlutterLocalEngineNotFound => { 119 | write!( 120 | f, 121 | "Could not find path for local Flutter engine. Either specify a valid \ 122 | 'local_engine_src_path', or make sure that engine project exists \ 123 | alongside the Flutter project." 124 | ) 125 | } 126 | } 127 | } 128 | } 129 | 130 | impl std::error::Error for BuildError {} 131 | 132 | pub(super) trait IOResultExt { 133 | fn wrap_error(self, operation: FileOperation, path: F) -> BuildResult 134 | where 135 | F: FnOnce() -> PathBuf; 136 | fn wrap_error_with_src( 137 | self, 138 | operation: FileOperation, 139 | path: F, 140 | source_path: G, 141 | ) -> BuildResult 142 | where 143 | F: FnOnce() -> PathBuf, 144 | G: FnOnce() -> PathBuf; 145 | } 146 | 147 | impl IOResultExt for io::Result { 148 | fn wrap_error(self, operation: FileOperation, path: F) -> BuildResult 149 | where 150 | F: FnOnce() -> PathBuf, 151 | { 152 | self.map_err(|e| BuildError::FileOperationError { 153 | operation, 154 | path: path(), 155 | source_path: None, 156 | source: e, 157 | }) 158 | } 159 | 160 | fn wrap_error_with_src( 161 | self, 162 | operation: FileOperation, 163 | path: F, 164 | source_path: G, 165 | ) -> BuildResult 166 | where 167 | F: FnOnce() -> PathBuf, 168 | G: FnOnce() -> PathBuf, 169 | { 170 | self.map_err(|e| BuildError::FileOperationError { 171 | operation, 172 | path: path(), 173 | source_path: Some(source_path()), 174 | source: e, 175 | }) 176 | } 177 | } 178 | --------------------------------------------------------------------------------