#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![allow(clippy::format_push_string)]
mod display;
mod error;
mod script;
mod shortcut;
#[cfg(all(target_os = "windows", feature = "platform-windows"))]
mod platform;
use bitflags::bitflags;
use tauri::plugin::{Builder as PluginBuilder, TauriPlugin};
use tauri::{Manager, Runtime};
pub use error::Error;
pub use script::Script;
pub use shortcut::{
KeyboardShortcut, KeyboardShortcutBuilder, ModifierKey, PointerEvent, PointerShortcut,
PointerShortcutBuilder, Shortcut, ShortcutKind,
};
#[cfg(all(target_os = "windows", feature = "platform-windows"))]
pub use platform::windows::PlatformOptions;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Flags: u32 {
const FIND = 1 << 0;
const CARET_BROWSING = 1 << 1;
const DEV_TOOLS = 1 << 2;
const DOWNLOADS = 1 << 3;
const FOCUS_MOVE = 1 << 4;
const RELOAD = 1 << 5;
const SOURCE = 1 << 6;
const OPEN = 1 << 7;
const PRINT = 1 << 8;
const CONTEXT_MENU = 1 << 9;
}
}
impl Flags {
pub fn keyboard() -> Self {
Self::all().difference(Self::pointer())
}
pub fn pointer() -> Self {
Self::CONTEXT_MENU
}
pub fn debug() -> Self {
if cfg!(debug_assertions) {
Self::all().difference(Self::CONTEXT_MENU | Self::DEV_TOOLS | Self::RELOAD)
} else {
Self::all()
}
}
}
impl Default for Flags {
fn default() -> Self {
Self::all()
}
}
pub struct Builder {
flags: Flags,
shortcuts: Vec<Box<dyn Shortcut>>,
check_origin: Option<String>,
#[cfg(all(target_os = "windows", feature = "platform-windows"))]
platform: PlatformOptions,
}
#[allow(clippy::derivable_impls)]
impl Default for Builder {
fn default() -> Self {
Self {
flags: Flags::default(),
shortcuts: Vec::new(),
check_origin: None,
#[cfg(all(target_os = "windows", feature = "platform-windows"))]
platform: PlatformOptions::default(),
}
}
}
impl Builder {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_flags(mut self, flags: Flags) -> Self {
self.flags = flags;
self
}
#[must_use]
pub fn shortcut<S>(mut self, shortcut: S) -> Self
where
S: Shortcut + 'static,
{
self.shortcuts.push(Box::new(shortcut));
self
}
#[must_use]
pub fn check_origin(mut self, origin: impl AsRef<str>) -> Self {
let origin = origin.as_ref().trim();
if !origin.is_empty() {
self.check_origin = Some(origin.to_owned());
}
self
}
#[must_use]
#[cfg(all(target_os = "windows", feature = "platform-windows"))]
pub fn platform(mut self, options: PlatformOptions) -> Self {
self.platform = options;
self
}
pub fn build<R: Runtime>(mut self) -> TauriPlugin<R> {
let script = self.create_script();
self
.plugin_builder()
.js_init_script(script.to_string())
.build()
}
pub fn build_with_manual_injection<R: Runtime>(mut self) -> TauriPlugin<R> {
let script = self.create_script();
self
.plugin_builder()
.setup(move |app, _| {
app.manage(script);
Ok(())
})
.build()
}
#[allow(clippy::unused_self)]
fn plugin_builder<R: Runtime>(self) -> PluginBuilder<R> {
#[allow(unused_mut)]
let mut builder = PluginBuilder::new("prevent-default");
#[cfg(all(target_os = "windows", feature = "platform-windows"))]
{
let options = self.platform;
builder = builder.on_webview_ready(move |webview| {
platform::windows::on_webview_ready(&webview, options.clone());
});
}
builder
}
fn create_script(&mut self) -> Script {
self.add_keyboard_shortcuts();
self.add_pointer_shortcuts();
let mut script = String::new();
for shortcut in &mut self.shortcuts {
match shortcut.kind() {
ShortcutKind::Keyboard(it) => {
let modifiers = it.modifiers();
let mut options = String::with_capacity(modifiers.len() * 12);
for modifier in modifiers {
options.push_str(&format!("{modifier}:true,"));
}
let options = options.trim_end_matches(',');
script.push_str(&format!("onKey('{}',{{{}}});", it.key(), options));
}
ShortcutKind::Pointer(it) => {
script.push_str(&format!("onPointer('{}');", it.event()));
}
}
}
let origin = self
.check_origin
.as_deref()
.map(|it| format!("const ORIGIN='{it}';"))
.unwrap_or_else(|| "const ORIGIN=null;".to_owned());
include_str!("../assets/script.js")
.trim()
.replace("/*ORIGIN*/", &origin)
.replace("/*SCRIPT*/", &script)
.into()
}
fn add_keyboard_shortcuts(&mut self) {
use shortcut::ModifierKey::{CtrlKey, ShiftKey};
macro_rules! on_key {
($($arg:literal)+) => {
$(
let shortcut = KeyboardShortcut::new($arg);
self.shortcuts.push(Box::new(shortcut));
)*
};
($modifiers:expr, $($arg:literal),+) => {
$(
let shortcut = KeyboardShortcut::with_modifiers($arg, $modifiers);
self.shortcuts.push(Box::new(shortcut));
)*
};
}
if self.flags.contains(Flags::FIND) {
on_key!("F3");
on_key!(&[CtrlKey], "f", "g");
on_key!(&[CtrlKey, ShiftKey], "g");
}
if self.flags.contains(Flags::CARET_BROWSING) {
on_key!("F7");
}
if self.flags.contains(Flags::DEV_TOOLS) {
on_key!(&[CtrlKey, ShiftKey], "i");
}
if self.flags.contains(Flags::DOWNLOADS) {
on_key!(&[CtrlKey], "j");
}
if self.flags.contains(Flags::FOCUS_MOVE) {
on_key!(&[ShiftKey], "Tab");
}
if self.flags.contains(Flags::RELOAD) {
on_key!("F5");
on_key!(&[CtrlKey], "F5");
on_key!(&[ShiftKey], "F5");
on_key!(&[CtrlKey], "r");
on_key!(&[CtrlKey, ShiftKey], "r");
}
if self.flags.contains(Flags::SOURCE) {
on_key!(&[CtrlKey], "u");
}
if self.flags.contains(Flags::OPEN) {
on_key!(&[CtrlKey], "o");
}
if self.flags.contains(Flags::PRINT) {
on_key!(&[CtrlKey], "p");
on_key!(&[CtrlKey, ShiftKey], "p");
}
}
fn add_pointer_shortcuts(&mut self) {
if self.flags.contains(Flags::CONTEXT_MENU) {
let shortcut = PointerShortcut::new(PointerEvent::ContextMenu);
self.shortcuts.push(Box::new(shortcut));
}
}
}
pub trait PreventDefault<R: Runtime> {
fn prevent_default_script(&self) -> Script;
fn try_prevent_default_script(&self) -> Option<Script>;
}
impl<R, T> PreventDefault<R> for T
where
R: Runtime,
T: Manager<R>,
{
fn prevent_default_script(&self) -> Script {
(*self.app_handle().state::<Script>()).clone()
}
fn try_prevent_default_script(&self) -> Option<Script> {
self
.app_handle()
.try_state::<Script>()
.as_deref()
.cloned()
}
}
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::default().build()
}
pub fn init_with_manual_injection<R: Runtime>() -> TauriPlugin<R> {
Builder::default().build_with_manual_injection()
}
pub fn with_flags<R: Runtime>(flags: Flags) -> TauriPlugin<R> {
Builder::new().with_flags(flags).build()
}
pub fn with_flags_and_manual_injection<R: Runtime>(flags: Flags) -> TauriPlugin<R> {
Builder::new()
.with_flags(flags)
.build_with_manual_injection()
}
pub fn debug<R: Runtime>() -> TauriPlugin<R> {
Builder::new().with_flags(Flags::debug()).build()
}
pub fn debug_with_manual_injection<R: Runtime>() -> TauriPlugin<R> {
Builder::new()
.with_flags(Flags::debug())
.build_with_manual_injection()
}