feat(plugins): add allowlist for current core plugins

pull/357/head
Lucas Nogueira 2 years ago
parent 702b7b36bd
commit 9d3e1aed69
No known key found for this signature in database
GPG Key ID: FFEA6C72E73482F1

14
Cargo.lock generated

@ -4960,7 +4960,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri" name = "tauri"
version = "2.0.0-alpha.8" version = "2.0.0-alpha.8"
source = "git+https://github.com/tauri-apps/tauri?branch=next#6d25c4d07fcf18c2a19ac4faa7d9bedd96d1a75f" source = "git+https://github.com/tauri-apps/tauri?branch=feat/plugin-allowlist#90b1db0f33ff65e5dc5cc3254326bda71fa72346"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes 1.4.0", "bytes 1.4.0",
@ -5010,7 +5010,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-build" name = "tauri-build"
version = "2.0.0-alpha.4" version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/tauri?branch=next#6d25c4d07fcf18c2a19ac4faa7d9bedd96d1a75f" source = "git+https://github.com/tauri-apps/tauri?branch=feat/plugin-allowlist#90b1db0f33ff65e5dc5cc3254326bda71fa72346"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cargo_toml", "cargo_toml",
@ -5030,7 +5030,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-codegen" name = "tauri-codegen"
version = "2.0.0-alpha.4" version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/tauri?branch=next#6d25c4d07fcf18c2a19ac4faa7d9bedd96d1a75f" source = "git+https://github.com/tauri-apps/tauri?branch=feat/plugin-allowlist#90b1db0f33ff65e5dc5cc3254326bda71fa72346"
dependencies = [ dependencies = [
"base64 0.21.0", "base64 0.21.0",
"brotli", "brotli",
@ -5055,7 +5055,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-macros" name = "tauri-macros"
version = "2.0.0-alpha.4" version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/tauri?branch=next#6d25c4d07fcf18c2a19ac4faa7d9bedd96d1a75f" source = "git+https://github.com/tauri-apps/tauri?branch=feat/plugin-allowlist#90b1db0f33ff65e5dc5cc3254326bda71fa72346"
dependencies = [ dependencies = [
"heck 0.4.1", "heck 0.4.1",
"proc-macro2", "proc-macro2",
@ -5449,7 +5449,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-runtime" name = "tauri-runtime"
version = "0.13.0-alpha.4" version = "0.13.0-alpha.4"
source = "git+https://github.com/tauri-apps/tauri?branch=next#6d25c4d07fcf18c2a19ac4faa7d9bedd96d1a75f" source = "git+https://github.com/tauri-apps/tauri?branch=feat/plugin-allowlist#90b1db0f33ff65e5dc5cc3254326bda71fa72346"
dependencies = [ dependencies = [
"gtk", "gtk",
"http", "http",
@ -5469,7 +5469,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-runtime-wry" name = "tauri-runtime-wry"
version = "0.13.0-alpha.4" version = "0.13.0-alpha.4"
source = "git+https://github.com/tauri-apps/tauri?branch=next#6d25c4d07fcf18c2a19ac4faa7d9bedd96d1a75f" source = "git+https://github.com/tauri-apps/tauri?branch=feat/plugin-allowlist#90b1db0f33ff65e5dc5cc3254326bda71fa72346"
dependencies = [ dependencies = [
"cocoa", "cocoa",
"gtk", "gtk",
@ -5489,7 +5489,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-utils" name = "tauri-utils"
version = "2.0.0-alpha.4" version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/tauri?branch=next#6d25c4d07fcf18c2a19ac4faa7d9bedd96d1a75f" source = "git+https://github.com/tauri-apps/tauri?branch=feat/plugin-allowlist#90b1db0f33ff65e5dc5cc3254326bda71fa72346"
dependencies = [ dependencies = [
"aes-gcm 0.10.1", "aes-gcm 0.10.1",
"brotli", "brotli",

@ -5,8 +5,8 @@ resolver = "2"
[workspace.dependencies] [workspace.dependencies]
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
log = "0.4" log = "0.4"
tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next" } tauri = { git = "https://github.com/tauri-apps/tauri", branch = "feat/plugin-allowlist" }
tauri-build = { git = "https://github.com/tauri-apps/tauri", branch = "next" } tauri-build = { git = "https://github.com/tauri-apps/tauri", branch = "feat/plugin-allowlist" }
serde_json = "1" serde_json = "1"
thiserror = "1" thiserror = "1"

@ -19,15 +19,15 @@ tiny_http = "0.11"
log.workspace = true log.workspace = true
tauri-plugin-app = { path = "../../../plugins/app" } tauri-plugin-app = { path = "../../../plugins/app" }
tauri-plugin-log = { path = "../../../plugins/log" } tauri-plugin-log = { path = "../../../plugins/log" }
tauri-plugin-fs = { path = "../../../plugins/fs" } tauri-plugin-fs = { path = "../../../plugins/fs", features = [] }
tauri-plugin-clipboard = { path = "../../../plugins/clipboard" } tauri-plugin-clipboard = { path = "../../../plugins/clipboard" }
tauri-plugin-dialog = { path = "../../../plugins/dialog" } tauri-plugin-dialog = { path = "../../../plugins/dialog" }
tauri-plugin-http = { path = "../../../plugins/http", features = [ "http-multipart" ] } tauri-plugin-http = { path = "../../../plugins/http", features = ["http-multipart"] }
tauri-plugin-notification = { path = "../../../plugins/notification", features = [ "windows7-compat" ] } tauri-plugin-notification = { path = "../../../plugins/notification", features = [ "windows7-compat" ] }
tauri-plugin-os = { path = "../../../plugins/os" } tauri-plugin-os = { path = "../../../plugins/os" }
tauri-plugin-process = { path = "../../../plugins/process" } tauri-plugin-process = { path = "../../../plugins/process" }
tauri-plugin-shell = { path = "../../../plugins/shell" } tauri-plugin-shell = { path = "../../../plugins/shell", features = [] }
tauri-plugin-updater = { path = "../../../plugins/updater" } tauri-plugin-updater = { path = "../../../plugins/updater", features = [] }
tauri-plugin-window = { path = "../../../plugins/window" } tauri-plugin-window = { path = "../../../plugins/window" }
[dependencies.tauri] [dependencies.tauri]
@ -42,7 +42,7 @@ features = [
] ]
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] [target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
tauri-plugin-cli = { path = "../../../plugins/cli" } tauri-plugin-cli = { path = "../../../plugins/cli", features = ["allow-get-matches"] }
tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut" } tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut" }
[target."cfg(target_os = \"windows\")".dependencies] [target."cfg(target_os = \"windows\")".dependencies]

@ -13,6 +13,9 @@
}, },
"plugins": { "plugins": {
"cli": { "cli": {
"allowlist": {
"getMatches": true
},
"description": "Tauri API example", "description": "Tauri API example",
"args": [ "args": [
{ {

@ -12,3 +12,6 @@ tauri.workspace = true
log.workspace = true log.workspace = true
thiserror.workspace = true thiserror.workspace = true
clap = { version = "4", features = ["string"] } clap = { version = "4", features = ["string"] }
[features]
allow-get-matches = []

@ -64,7 +64,7 @@ interface CliMatches {
* @since 1.0.0 * @since 1.0.0
*/ */
async function getMatches(): Promise<CliMatches> { async function getMatches(): Promise<CliMatches> {
return await invoke("plugin:cli|cli_matches"); return await invoke("plugin:cli|get_matches");
} }
export type { ArgMatch, SubcommandMatch, CliMatches }; export type { ArgMatch, SubcommandMatch, CliMatches };

@ -1,6 +1,6 @@
use tauri::{ use tauri::{
plugin::{Builder, PluginApi, TauriPlugin}, plugin::{Builder, PluginApi, TauriPlugin},
AppHandle, Manager, Runtime, State, Manager, Runtime,
}; };
mod config; mod config;
@ -29,14 +29,21 @@ impl<R: Runtime, T: Manager<R>> CliExt<R> for T {
} }
} }
#[cfg(feature = "allow-get-matches")]
#[tauri::command] #[tauri::command]
fn cli_matches<R: Runtime>(_app: AppHandle<R>, cli: State<'_, Cli<R>>) -> Result<parser::Matches> { fn get_matches<R: tauri::Runtime>(
_app: tauri::AppHandle<R>,
cli: tauri::State<'_, Cli<R>>,
) -> Result<parser::Matches> {
cli.matches() cli.matches()
} }
pub fn init<R: Runtime>() -> TauriPlugin<R, Config> { pub fn init<R: Runtime>() -> TauriPlugin<R, Config> {
Builder::new("cli") Builder::new("cli")
.invoke_handler(tauri::generate_handler![cli_matches]) .invoke_handler(tauri::generate_handler![
#[cfg(feature = "allow-get-matches")]
get_matches
])
.setup(|app, api| { .setup(|app, api| {
app.manage(Cli(api)); app.manage(Cli(api));
Ok(()) Ok(())

@ -17,3 +17,7 @@ thiserror.workspace = true
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] [target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
arboard = "3" arboard = "3"
[features]
allow-write = []
allow-read = []

@ -2,10 +2,13 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
#![allow(unused_imports)]
use tauri::{command, AppHandle, Runtime, State}; use tauri::{command, AppHandle, Runtime, State};
use crate::{ClipKind, Clipboard, ClipboardContents, Result}; use crate::{ClipKind, Clipboard, ClipboardContents, Result};
#[cfg(feature = "allow-write")]
#[command] #[command]
pub(crate) async fn write<R: Runtime>( pub(crate) async fn write<R: Runtime>(
_app: AppHandle<R>, _app: AppHandle<R>,
@ -15,6 +18,7 @@ pub(crate) async fn write<R: Runtime>(
clipboard.write(data) clipboard.write(data)
} }
#[cfg(feature = "allow-read")]
#[command] #[command]
pub(crate) async fn read<R: Runtime>( pub(crate) async fn read<R: Runtime>(
_app: AppHandle<R>, _app: AppHandle<R>,

@ -39,7 +39,12 @@ impl<R: Runtime, T: Manager<R>> crate::ClipboardExt<R> for T {
/// Initializes the plugin. /// Initializes the plugin.
pub fn init<R: Runtime>() -> TauriPlugin<R> { pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("clipboard") Builder::new("clipboard")
.invoke_handler(tauri::generate_handler![commands::write, commands::read]) .invoke_handler(tauri::generate_handler![
#[cfg(feature = "allow-write")]
commands::write,
#[cfg(feature = "allow-read")]
commands::read
])
.setup(|app, api| { .setup(|app, api| {
#[cfg(mobile)] #[cfg(mobile)]
let clipboard = mobile::init(app, api)?; let clipboard = mobile::init(app, api)?;

@ -24,3 +24,10 @@ raw-window-handle = "0.5"
[build-dependencies] [build-dependencies]
tauri-build.workspace = true tauri-build.workspace = true
[features]
allow-open = []
allow-save = []
allow-message = []
allow-ask = []
allow-confirm = []

@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
#![allow(unused_imports)]
use std::path::PathBuf; use std::path::PathBuf;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -86,6 +88,7 @@ fn set_default_path<R: Runtime>(
} }
} }
#[cfg(feature = "allow-open")]
#[command] #[command]
pub(crate) async fn open<R: Runtime>( pub(crate) async fn open<R: Runtime>(
window: Window<R>, window: Window<R>,
@ -161,6 +164,7 @@ pub(crate) async fn open<R: Runtime>(
Ok(res) Ok(res)
} }
#[cfg(feature = "allow-save")]
#[allow(unused_variables)] #[allow(unused_variables)]
#[command] #[command]
pub(crate) async fn save<R: Runtime>( pub(crate) async fn save<R: Runtime>(
@ -235,6 +239,7 @@ fn message_dialog<R: Runtime>(
builder.blocking_show() builder.blocking_show()
} }
#[cfg(feature = "allow-message")]
#[command] #[command]
pub(crate) async fn message<R: Runtime>( pub(crate) async fn message<R: Runtime>(
window: Window<R>, window: Window<R>,
@ -255,6 +260,7 @@ pub(crate) async fn message<R: Runtime>(
)) ))
} }
#[cfg(feature = "allow-ask")]
#[command] #[command]
pub(crate) async fn ask<R: Runtime>( pub(crate) async fn ask<R: Runtime>(
window: Window<R>, window: Window<R>,
@ -276,6 +282,7 @@ pub(crate) async fn ask<R: Runtime>(
)) ))
} }
#[cfg(feature = "allow-confirm")]
#[command] #[command]
pub(crate) async fn confirm<R: Runtime>( pub(crate) async fn confirm<R: Runtime>(
window: Window<R>, window: Window<R>,

@ -71,10 +71,15 @@ impl<R: Runtime> Dialog<R> {
pub fn init<R: Runtime>() -> TauriPlugin<R> { pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("dialog") Builder::new("dialog")
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
#[cfg(feature = "allow-open")]
commands::open, commands::open,
#[cfg(feature = "allow-save")]
commands::save, commands::save,
#[cfg(feature = "allow-message")]
commands::message, commands::message,
#[cfg(feature = "allow-ask")]
commands::ask, commands::ask,
#[cfg(feature = "allow-confirm")]
commands::confirm commands::confirm
]) ])
.setup(|app, api| { .setup(|app, api| {

@ -16,3 +16,15 @@ thiserror.workspace = true
anyhow = "1" anyhow = "1"
uuid = { version = "1", features = ["v4"] } uuid = { version = "1", features = ["v4"] }
glob = "0.3" glob = "0.3"
[features]
allow-read-file = []
allow-write-file = []
allow-read-dir = []
allow-copy-file = []
allow-create-dir = []
allow-remove-dir = []
allow-remove-file = []
allow-rename-file = []
allow-exists = []
allow-metadata = []

@ -1,3 +1,5 @@
#![allow(unused_imports)]
use crate::Scope; use crate::Scope;
use anyhow::Context; use anyhow::Context;
use serde::{Deserialize, Serialize, Serializer}; use serde::{Deserialize, Serialize, Serializer};
@ -6,15 +8,10 @@ use tauri::{
Manager, Runtime, Window, Manager, Runtime, Window,
}; };
#[cfg(unix)]
use std::os::unix::fs::{MetadataExt, PermissionsExt};
#[cfg(windows)]
use std::os::windows::fs::MetadataExt;
use std::{ use std::{
fs::{self, symlink_metadata, File}, fs::{self, File},
io::Write, io::Write,
path::{Path, PathBuf}, path::{Path, PathBuf},
time::{SystemTime, UNIX_EPOCH},
}; };
use crate::{Error, FsExt, Result}; use crate::{Error, FsExt, Result};
@ -36,6 +33,7 @@ impl Serialize for CommandError {
} }
} }
#[allow(dead_code)]
type CommandResult<T> = std::result::Result<T, CommandError>; type CommandResult<T> = std::result::Result<T, CommandError>;
/// The options for the directory functions on the file system API. /// The options for the directory functions on the file system API.
@ -57,6 +55,7 @@ pub struct FileOperationOptions {
pub dir: Option<BaseDirectory>, pub dir: Option<BaseDirectory>,
} }
#[allow(dead_code)]
fn resolve_path<R: Runtime>( fn resolve_path<R: Runtime>(
window: &Window<R>, window: &Window<R>,
path: SafePathBuf, path: SafePathBuf,
@ -77,6 +76,7 @@ fn resolve_path<R: Runtime>(
} }
} }
#[cfg(feature = "allow-read-file")]
#[tauri::command] #[tauri::command]
pub fn read_file<R: Runtime>( pub fn read_file<R: Runtime>(
window: Window<R>, window: Window<R>,
@ -89,6 +89,7 @@ pub fn read_file<R: Runtime>(
.map_err(Into::into) .map_err(Into::into)
} }
#[cfg(feature = "allow-read-file")]
#[tauri::command] #[tauri::command]
pub fn read_text_file<R: Runtime>( pub fn read_text_file<R: Runtime>(
window: Window<R>, window: Window<R>,
@ -101,6 +102,7 @@ pub fn read_text_file<R: Runtime>(
.map_err(Into::into) .map_err(Into::into)
} }
#[cfg(feature = "allow-write-file")]
#[tauri::command] #[tauri::command]
pub fn write_file<R: Runtime>( pub fn write_file<R: Runtime>(
window: Window<R>, window: Window<R>,
@ -119,84 +121,104 @@ pub fn write_file<R: Runtime>(
}) })
} }
#[derive(Clone, Copy)] #[cfg(feature = "allow-read-dir")]
struct ReadDirOptions<'a> { pub use read_dir::*;
pub scope: Option<&'a Scope>,
}
#[derive(Debug, Serialize)] #[cfg(feature = "allow-read-dir")]
#[non_exhaustive] mod read_dir {
pub struct DiskEntry { use std::{
/// The path to the entry. fs::{self, symlink_metadata},
pub path: PathBuf, path::{Path, PathBuf},
/// The name of the entry (file name with extension or directory name). };
pub name: Option<String>,
/// The children of this entry if it's a directory. use anyhow::Context;
#[serde(skip_serializing_if = "Option::is_none")] use serde::Serialize;
pub children: Option<Vec<DiskEntry>>, use tauri::{path::SafePathBuf, Runtime, Window};
}
use crate::{FsExt, Result, Scope};
use super::{resolve_path, CommandResult, DirOperationOptions};
fn read_dir_with_options<P: AsRef<Path>>( #[derive(Clone, Copy)]
path: P, struct ReadDirOptions<'a> {
recursive: bool, pub scope: Option<&'a Scope>,
options: ReadDirOptions<'_>, }
) -> Result<Vec<DiskEntry>> {
let mut files_and_dirs: Vec<DiskEntry> = vec![]; #[derive(Debug, Serialize)]
for entry in fs::read_dir(path)? { #[non_exhaustive]
let path = entry?.path(); pub struct DiskEntry {
let path_as_string = path.display().to_string(); /// The path to the entry.
pub path: PathBuf,
if let Ok(flag) = path.metadata().map(|m| m.is_dir()) { /// The name of the entry (file name with extension or directory name).
let is_symlink = symlink_metadata(&path).map(|md| md.is_symlink())?; pub name: Option<String>,
files_and_dirs.push(DiskEntry { /// The children of this entry if it's a directory.
path: path.clone(), #[serde(skip_serializing_if = "Option::is_none")]
children: if flag { pub children: Option<Vec<DiskEntry>>,
Some( }
if recursive
&& (!is_symlink fn read_dir_with_options<P: AsRef<Path>>(
|| options.scope.map(|s| s.is_allowed(&path)).unwrap_or(true)) path: P,
{ recursive: bool,
read_dir_with_options(&path_as_string, true, options)? options: ReadDirOptions<'_>,
} else { ) -> Result<Vec<DiskEntry>> {
vec![] let mut files_and_dirs: Vec<DiskEntry> = vec![];
}, for entry in fs::read_dir(path)? {
) let path = entry?.path();
} else { let path_as_string = path.display().to_string();
None
}, if let Ok(flag) = path.metadata().map(|m| m.is_dir()) {
name: path let is_symlink = symlink_metadata(&path).map(|md| md.is_symlink())?;
.file_name() files_and_dirs.push(DiskEntry {
.map(|name| name.to_string_lossy()) path: path.clone(),
.map(|name| name.to_string()), children: if flag {
}); Some(
if recursive
&& (!is_symlink
|| options.scope.map(|s| s.is_allowed(&path)).unwrap_or(true))
{
read_dir_with_options(&path_as_string, true, options)?
} else {
vec![]
},
)
} else {
None
},
name: path
.file_name()
.map(|name| name.to_string_lossy())
.map(|name| name.to_string()),
});
}
} }
Result::Ok(files_and_dirs)
} }
Result::Ok(files_and_dirs)
}
#[tauri::command] #[tauri::command]
pub fn read_dir<R: Runtime>( pub fn read_dir<R: Runtime>(
window: Window<R>, window: Window<R>,
path: SafePathBuf, path: SafePathBuf,
options: Option<DirOperationOptions>, options: Option<DirOperationOptions>,
) -> CommandResult<Vec<DiskEntry>> { ) -> CommandResult<Vec<DiskEntry>> {
let (recursive, dir) = if let Some(options_value) = options { let (recursive, dir) = if let Some(options_value) = options {
(options_value.recursive, options_value.dir) (options_value.recursive, options_value.dir)
} else { } else {
(false, None) (false, None)
}; };
let resolved_path = resolve_path(&window, path, dir)?; let resolved_path = resolve_path(&window, path, dir)?;
read_dir_with_options( read_dir_with_options(
&resolved_path, &resolved_path,
recursive, recursive,
ReadDirOptions { ReadDirOptions {
scope: Some(window.fs_scope()), scope: Some(window.fs_scope()),
}, },
) )
.with_context(|| format!("path: {}", resolved_path.display())) .with_context(|| format!("path: {}", resolved_path.display()))
.map_err(Into::into) .map_err(Into::into)
}
} }
#[cfg(feature = "allow-copy-file")]
#[tauri::command] #[tauri::command]
pub fn copy_file<R: Runtime>( pub fn copy_file<R: Runtime>(
window: Window<R>, window: Window<R>,
@ -222,6 +244,7 @@ pub fn copy_file<R: Runtime>(
Ok(()) Ok(())
} }
#[cfg(feature = "allow-create-dir")]
#[tauri::command] #[tauri::command]
pub fn create_dir<R: Runtime>( pub fn create_dir<R: Runtime>(
window: Window<R>, window: Window<R>,
@ -245,6 +268,7 @@ pub fn create_dir<R: Runtime>(
Ok(()) Ok(())
} }
#[cfg(feature = "allow-remove-dir")]
#[tauri::command] #[tauri::command]
pub fn remove_dir<R: Runtime>( pub fn remove_dir<R: Runtime>(
window: Window<R>, window: Window<R>,
@ -268,6 +292,7 @@ pub fn remove_dir<R: Runtime>(
Ok(()) Ok(())
} }
#[cfg(feature = "allow-remove-file")]
#[tauri::command] #[tauri::command]
pub fn remove_file<R: Runtime>( pub fn remove_file<R: Runtime>(
window: Window<R>, window: Window<R>,
@ -280,6 +305,7 @@ pub fn remove_file<R: Runtime>(
Ok(()) Ok(())
} }
#[cfg(feature = "allow-rename-file")]
#[tauri::command] #[tauri::command]
pub fn rename_file<R: Runtime>( pub fn rename_file<R: Runtime>(
window: Window<R>, window: Window<R>,
@ -300,6 +326,7 @@ pub fn rename_file<R: Runtime>(
Ok(()) Ok(())
} }
#[cfg(feature = "allow-exists")]
#[tauri::command] #[tauri::command]
pub fn exists<R: Runtime>( pub fn exists<R: Runtime>(
window: Window<R>, window: Window<R>,
@ -310,86 +337,106 @@ pub fn exists<R: Runtime>(
Ok(resolved_path.exists()) Ok(resolved_path.exists())
} }
#[derive(Serialize)] #[cfg(feature = "allow-metadata")]
#[serde(rename_all = "camelCase")] pub use metadata::*;
pub struct Permissions {
readonly: bool,
#[cfg(unix)]
mode: u32,
}
#[cfg(unix)] #[cfg(feature = "allow-metadata")]
#[derive(Serialize)] mod metadata {
#[serde(rename_all = "camelCase")] use std::{
pub struct UnixMetadata { path::PathBuf,
dev: u64, time::{SystemTime, UNIX_EPOCH},
ino: u64, };
mode: u32,
nlink: u64, use serde::Serialize;
uid: u32,
gid: u32,
rdev: u64,
blksize: u64,
blocks: u64,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Metadata {
accessed_at_ms: u64,
created_at_ms: u64,
modified_at_ms: u64,
is_dir: bool,
is_file: bool,
is_symlink: bool,
size: u64,
permissions: Permissions,
#[cfg(unix)] #[cfg(unix)]
#[serde(flatten)] use std::os::unix::fs::{MetadataExt, PermissionsExt};
unix: UnixMetadata,
#[cfg(windows)] #[cfg(windows)]
file_attributes: u32, use std::os::windows::fs::MetadataExt;
}
fn system_time_to_ms(time: std::io::Result<SystemTime>) -> u64 { use crate::Result;
time.map(|t| {
let duration_since_epoch = t.duration_since(UNIX_EPOCH).unwrap();
duration_since_epoch.as_millis() as u64
})
.unwrap_or_default()
}
#[tauri::command] #[derive(Serialize)]
pub async fn metadata(path: PathBuf) -> Result<Metadata> { #[serde(rename_all = "camelCase")]
let metadata = std::fs::metadata(path)?; pub struct Permissions {
let file_type = metadata.file_type(); readonly: bool,
let permissions = metadata.permissions(); #[cfg(unix)]
Ok(Metadata { mode: u32,
accessed_at_ms: system_time_to_ms(metadata.accessed()), }
created_at_ms: system_time_to_ms(metadata.created()),
modified_at_ms: system_time_to_ms(metadata.modified()), #[cfg(unix)]
is_dir: file_type.is_dir(), #[derive(Serialize)]
is_file: file_type.is_file(), #[serde(rename_all = "camelCase")]
is_symlink: file_type.is_symlink(), pub struct UnixMetadata {
size: metadata.len(), dev: u64,
permissions: Permissions { ino: u64,
readonly: permissions.readonly(), mode: u32,
#[cfg(unix)] nlink: u64,
mode: permissions.mode(), uid: u32,
}, gid: u32,
rdev: u64,
blksize: u64,
blocks: u64,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Metadata {
accessed_at_ms: u64,
created_at_ms: u64,
modified_at_ms: u64,
is_dir: bool,
is_file: bool,
is_symlink: bool,
size: u64,
permissions: Permissions,
#[cfg(unix)] #[cfg(unix)]
unix: UnixMetadata { #[serde(flatten)]
dev: metadata.dev(), unix: UnixMetadata,
ino: metadata.ino(),
mode: metadata.mode(),
nlink: metadata.nlink(),
uid: metadata.uid(),
gid: metadata.gid(),
rdev: metadata.rdev(),
blksize: metadata.blksize(),
blocks: metadata.blocks(),
},
#[cfg(windows)] #[cfg(windows)]
file_attributes: metadata.file_attributes(), file_attributes: u32,
}) }
fn system_time_to_ms(time: std::io::Result<SystemTime>) -> u64 {
time.map(|t| {
let duration_since_epoch = t.duration_since(UNIX_EPOCH).unwrap();
duration_since_epoch.as_millis() as u64
})
.unwrap_or_default()
}
#[tauri::command]
pub async fn metadata(path: PathBuf) -> Result<Metadata> {
let metadata = std::fs::metadata(path)?;
let file_type = metadata.file_type();
let permissions = metadata.permissions();
Ok(Metadata {
accessed_at_ms: system_time_to_ms(metadata.accessed()),
created_at_ms: system_time_to_ms(metadata.created()),
modified_at_ms: system_time_to_ms(metadata.modified()),
is_dir: file_type.is_dir(),
is_file: file_type.is_file(),
is_symlink: file_type.is_symlink(),
size: metadata.len(),
permissions: Permissions {
readonly: permissions.readonly(),
#[cfg(unix)]
mode: permissions.mode(),
},
#[cfg(unix)]
unix: UnixMetadata {
dev: metadata.dev(),
ino: metadata.ino(),
mode: metadata.mode(),
nlink: metadata.nlink(),
uid: metadata.uid(),
gid: metadata.gid(),
rdev: metadata.rdev(),
blksize: metadata.blksize(),
blocks: metadata.blocks(),
},
#[cfg(windows)]
file_attributes: metadata.file_attributes(),
})
}
} }

@ -37,16 +37,27 @@ impl<R: Runtime, T: Manager<R>> FsExt<R> for T {
pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> { pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
PluginBuilder::<R, Option<Config>>::new("fs") PluginBuilder::<R, Option<Config>>::new("fs")
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
#[cfg(feature = "allow-read-file")]
commands::read_file, commands::read_file,
#[cfg(feature = "allow-read-file")]
commands::read_text_file, commands::read_text_file,
#[cfg(feature = "allow-write-file")]
commands::write_file, commands::write_file,
#[cfg(feature = "allow-read-dir")]
commands::read_dir, commands::read_dir,
#[cfg(feature = "allow-copy-file")]
commands::copy_file, commands::copy_file,
#[cfg(feature = "allow-create-dir")]
commands::create_dir, commands::create_dir,
#[cfg(feature = "allow-remove-dir")]
commands::remove_dir, commands::remove_dir,
#[cfg(feature = "allow-remove-file")]
commands::remove_file, commands::remove_file,
#[cfg(feature = "allow-rename-file")]
commands::rename_file, commands::rename_file,
#[cfg(feature = "allow-exists")]
commands::exists, commands::exists,
#[cfg(feature = "allow-metadata")]
commands::metadata commands::metadata
]) ])
.setup(|app: &tauri::AppHandle<R>, api| { .setup(|app: &tauri::AppHandle<R>, api| {

@ -12,3 +12,8 @@ tauri.workspace = true
log.workspace = true log.workspace = true
thiserror.workspace = true thiserror.workspace = true
global-hotkey = "0.2.1" global-hotkey = "0.2.1"
[features]
allow-register = []
allow-unregister = []
allow-is-registered = []

@ -9,7 +9,7 @@ use global_hotkey::{GlobalHotKeyEvent, GlobalHotKeyManager};
use tauri::{ use tauri::{
api::ipc::CallbackFn, api::ipc::CallbackFn,
plugin::{Builder as PluginBuilder, TauriPlugin}, plugin::{Builder as PluginBuilder, TauriPlugin},
AppHandle, Manager, Runtime, State, Window, AppHandle, Manager, Runtime, Window,
}; };
mod error; mod error;
@ -186,6 +186,7 @@ fn acquire_manager(
.map_err(|e| Error::GlobalHotkey(e.to_string())) .map_err(|e| Error::GlobalHotkey(e.to_string()))
} }
#[allow(dead_code)]
fn parse_shortcut<S: AsRef<str>>(shortcut: S) -> Result<Shortcut> { fn parse_shortcut<S: AsRef<str>>(shortcut: S) -> Result<Shortcut> {
shortcut.as_ref().parse().map_err(Into::into) shortcut.as_ref().parse().map_err(Into::into)
} }
@ -200,10 +201,11 @@ where
.map_err(|e| Error::GlobalHotkey(e.to_string())) .map_err(|e| Error::GlobalHotkey(e.to_string()))
} }
#[cfg(feature = "allow-register")]
#[tauri::command] #[tauri::command]
fn register<R: Runtime>( fn register<R: Runtime>(
window: Window<R>, window: Window<R>,
global_shortcut: State<'_, GlobalShortcut<R>>, global_shortcut: tauri::State<'_, GlobalShortcut<R>>,
shortcut: String, shortcut: String,
handler: CallbackFn, handler: CallbackFn,
) -> Result<()> { ) -> Result<()> {
@ -213,10 +215,11 @@ fn register<R: Runtime>(
) )
} }
#[cfg(feature = "allow-register")]
#[tauri::command] #[tauri::command]
fn register_all<R: Runtime>( fn register_all<R: Runtime>(
window: Window<R>, window: Window<R>,
global_shortcut: State<'_, GlobalShortcut<R>>, global_shortcut: tauri::State<'_, GlobalShortcut<R>>,
shortcuts: Vec<String>, shortcuts: Vec<String>,
handler: CallbackFn, handler: CallbackFn,
) -> Result<()> { ) -> Result<()> {
@ -227,19 +230,21 @@ fn register_all<R: Runtime>(
global_shortcut.register_all_internal(hotkeys, ShortcutSource::Ipc { window, handler }) global_shortcut.register_all_internal(hotkeys, ShortcutSource::Ipc { window, handler })
} }
#[cfg(feature = "allow-unregister")]
#[tauri::command] #[tauri::command]
fn unregister<R: Runtime>( fn unregister<R: Runtime>(
_app: AppHandle<R>, _app: AppHandle<R>,
global_shortcut: State<'_, GlobalShortcut<R>>, global_shortcut: tauri::State<'_, GlobalShortcut<R>>,
shortcut: String, shortcut: String,
) -> Result<()> { ) -> Result<()> {
global_shortcut.unregister(parse_shortcut(shortcut)?) global_shortcut.unregister(parse_shortcut(shortcut)?)
} }
#[cfg(feature = "allow-unregister")]
#[tauri::command] #[tauri::command]
fn unregister_all<R: Runtime>( fn unregister_all<R: Runtime>(
_app: AppHandle<R>, _app: AppHandle<R>,
global_shortcut: State<'_, GlobalShortcut<R>>, global_shortcut: tauri::State<'_, GlobalShortcut<R>>,
shortcuts: Vec<String>, shortcuts: Vec<String>,
) -> Result<()> { ) -> Result<()> {
let mut hotkeys = Vec::new(); let mut hotkeys = Vec::new();
@ -249,10 +254,11 @@ fn unregister_all<R: Runtime>(
global_shortcut.unregister_all(hotkeys) global_shortcut.unregister_all(hotkeys)
} }
#[cfg(feature = "allow-is-registered")]
#[tauri::command] #[tauri::command]
fn is_registered<R: Runtime>( fn is_registered<R: Runtime>(
_app: AppHandle<R>, _app: AppHandle<R>,
global_shortcut: State<'_, GlobalShortcut<R>>, global_shortcut: tauri::State<'_, GlobalShortcut<R>>,
shortcut: String, shortcut: String,
) -> Result<bool> { ) -> Result<bool> {
Ok(global_shortcut.is_registered(parse_shortcut(shortcut)?)) Ok(global_shortcut.is_registered(parse_shortcut(shortcut)?))
@ -278,10 +284,15 @@ impl Builder {
let handler = self.handler; let handler = self.handler;
PluginBuilder::new("globalShortcut") PluginBuilder::new("globalShortcut")
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
#[cfg(feature = "allow-register")]
register, register,
#[cfg(feature = "allow-register")]
register_all, register_all,
#[cfg(feature = "allow-unregister")]
unregister, unregister,
#[cfg(feature = "allow-unregister")]
unregister_all, unregister_all,
#[cfg(feature = "allow-is-registered")]
is_registered is_registered
]) ])
.setup(move |app, _api| { .setup(move |app, _api| {

@ -23,3 +23,4 @@ http-multipart = [ "reqwest/multipart" ]
native-tls = [ "reqwest/native-tls" ] native-tls = [ "reqwest/native-tls" ]
native-tls-vendored = [ "reqwest/native-tls-vendored" ] native-tls-vendored = [ "reqwest/native-tls-vendored" ]
rustls-tls = [ "reqwest/rustls-tls" ] rustls-tls = [ "reqwest/rustls-tls" ]
allow-request = []

@ -1,3 +1,5 @@
#![allow(unused_imports, dead_code)]
use tauri::{path::SafePathBuf, AppHandle, Runtime, State}; use tauri::{path::SafePathBuf, AppHandle, Runtime, State};
use tauri_plugin_fs::FsExt; use tauri_plugin_fs::FsExt;
@ -8,6 +10,7 @@ use client::{Body, ClientBuilder, FilePart, FormPart, HttpRequestBuilder, Respon
pub use client::Client; pub use client::Client;
#[cfg(feature = "allow-request")]
#[tauri::command] #[tauri::command]
pub async fn create_client<R: Runtime>( pub async fn create_client<R: Runtime>(
_app: AppHandle<R>, _app: AppHandle<R>,
@ -21,6 +24,7 @@ pub async fn create_client<R: Runtime>(
Ok(id) Ok(id)
} }
#[cfg(feature = "allow-request")]
#[tauri::command] #[tauri::command]
pub async fn drop_client<R: Runtime>( pub async fn drop_client<R: Runtime>(
_app: AppHandle<R>, _app: AppHandle<R>,
@ -32,6 +36,7 @@ pub async fn drop_client<R: Runtime>(
Ok(()) Ok(())
} }
#[cfg(feature = "allow-request")]
#[tauri::command] #[tauri::command]
pub async fn request<R: Runtime>( pub async fn request<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,

@ -16,8 +16,8 @@ pub use error::Error;
type Result<T> = std::result::Result<T, Error>; type Result<T> = std::result::Result<T, Error>;
type ClientId = u32; type ClientId = u32;
#[allow(dead_code)]
pub struct Http<R: Runtime> { pub struct Http<R: Runtime> {
#[allow(dead_code)]
app: AppHandle<R>, app: AppHandle<R>,
pub(crate) clients: Mutex<HashMap<ClientId, commands::Client>>, pub(crate) clients: Mutex<HashMap<ClientId, commands::Client>>,
pub(crate) scope: scope::Scope, pub(crate) scope: scope::Scope,
@ -38,8 +38,11 @@ impl<R: Runtime, T: Manager<R>> HttpExt<R> for T {
pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> { pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
Builder::<R, Option<Config>>::new("http") Builder::<R, Option<Config>>::new("http")
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
#[cfg(feature = "allow-request")]
commands::create_client, commands::create_client,
#[cfg(feature = "allow-request")]
commands::drop_client, commands::drop_client,
#[cfg(feature = "allow-request")]
commands::request commands::request
]) ])
.setup(|app, api| { .setup(|app, api| {

@ -9,6 +9,7 @@ use reqwest::Url;
/// Scope for filesystem access. /// Scope for filesystem access.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Scope { pub struct Scope {
#[allow(dead_code)]
allowed_urls: Vec<Pattern>, allowed_urls: Vec<Pattern>,
} }
@ -29,6 +30,7 @@ impl Scope {
} }
/// Determines if the given URL is allowed on this scope. /// Determines if the given URL is allowed on this scope.
#[allow(dead_code)]
pub fn is_allowed(&self, url: &Url) -> bool { pub fn is_allowed(&self, url: &Url) -> bool {
self.allowed_urls self.allowed_urls
.iter() .iter()

@ -29,3 +29,5 @@ win7-notifications = { version = "0.3.1", optional = true }
[features] [features]
windows7-compat = [ "win7-notifications" ] windows7-compat = [ "win7-notifications" ]
allow-request-permission = []
allow-notify = []

@ -4,7 +4,7 @@
use tauri::{command, AppHandle, Runtime, State}; use tauri::{command, AppHandle, Runtime, State};
use crate::{Notification, NotificationData, PermissionState, Result}; use crate::{Notification, PermissionState, Result};
#[command] #[command]
pub(crate) async fn is_permission_granted<R: Runtime>( pub(crate) async fn is_permission_granted<R: Runtime>(
@ -19,6 +19,7 @@ pub(crate) async fn is_permission_granted<R: Runtime>(
} }
} }
#[cfg(feature = "allow-request-permission")]
#[command] #[command]
pub(crate) async fn request_permission<R: Runtime>( pub(crate) async fn request_permission<R: Runtime>(
_app: AppHandle<R>, _app: AppHandle<R>,
@ -27,11 +28,12 @@ pub(crate) async fn request_permission<R: Runtime>(
notification.request_permission() notification.request_permission()
} }
#[cfg(feature = "allow-notify")]
#[command] #[command]
pub(crate) async fn notify<R: Runtime>( pub(crate) async fn notify<R: Runtime>(
_app: AppHandle<R>, _app: AppHandle<R>,
notification: State<'_, Notification<R>>, notification: State<'_, Notification<R>>,
options: NotificationData, options: crate::NotificationData,
) -> Result<()> { ) -> Result<()> {
let mut builder = notification.builder(); let mut builder = notification.builder();
builder.data = options; builder.data = options;

@ -214,7 +214,9 @@ impl<R: Runtime, T: Manager<R>> crate::NotificationExt<R> for T {
pub fn init<R: Runtime>() -> TauriPlugin<R> { pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("notification") Builder::new("notification")
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
#[cfg(feature = "allow-notify")]
commands::notify, commands::notify,
#[cfg(feature = "allow-request-permission")]
commands::request_permission, commands::request_permission,
commands::is_permission_granted commands::is_permission_granted
]) ])

@ -7,3 +7,7 @@ license.workspace = true
[dependencies] [dependencies]
tauri.workspace = true tauri.workspace = true
[features]
allow-exit = []
allow-restart = []

@ -1,10 +1,14 @@
#![allow(unused_imports)]
use tauri::{AppHandle, Runtime}; use tauri::{AppHandle, Runtime};
#[cfg(feature = "allow-exit")]
#[tauri::command] #[tauri::command]
pub fn exit<R: Runtime>(app: AppHandle<R>, code: i32) { pub fn exit<R: Runtime>(app: AppHandle<R>, code: i32) {
app.exit(code) app.exit(code)
} }
#[cfg(feature = "allow-restart")]
#[tauri::command] #[tauri::command]
pub fn restart<R: Runtime>(app: AppHandle<R>) { pub fn restart<R: Runtime>(app: AppHandle<R>) {
app.restart() app.restart()

@ -7,6 +7,11 @@ mod commands;
pub fn init<R: Runtime>() -> TauriPlugin<R> { pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("process") Builder::new("process")
.invoke_handler(tauri::generate_handler![commands::exit, commands::restart]) .invoke_handler(tauri::generate_handler![
#[cfg(feature = "allow-exit")]
commands::exit,
#[cfg(feature = "allow-restart")]
commands::restart
])
.build() .build()
} }

@ -15,4 +15,10 @@ shared_child = "1"
regex = "1" regex = "1"
open = "4" open = "4"
encoding_rs = "0.8" encoding_rs = "0.8"
os_pipe = "1" os_pipe = "1"
[features]
allow-execute = []
allow-stdin-write = []
allow-kill = []
allow-open = []

@ -1,177 +1,196 @@
use std::{collections::HashMap, path::PathBuf, string::FromUtf8Error}; #![allow(unused_imports)]
use encoding_rs::Encoding;
use serde::{Deserialize, Serialize};
use tauri::{api::ipc::CallbackFn, Manager, Runtime, State, Window}; use tauri::{api::ipc::CallbackFn, Manager, Runtime, State, Window};
use crate::{ use crate::{
open::Program, open::Program,
process::{CommandEvent, TerminatedPayload}, process::{CommandEvent, TerminatedPayload},
scope::ExecuteArgs,
Shell, Shell,
}; };
#[allow(dead_code)]
type ChildId = u32; type ChildId = u32;
#[derive(Debug, Clone, Serialize)] #[cfg(feature = "allow-execute")]
#[serde(tag = "event", content = "payload")] pub use execute::*;
#[non_exhaustive]
enum JSCommandEvent { #[cfg(feature = "allow-execute")]
/// Stderr bytes until a newline (\n) or carriage return (\r) is found. mod execute {
Stderr(Buffer), use std::{collections::HashMap, path::PathBuf, string::FromUtf8Error};
/// Stdout bytes until a newline (\n) or carriage return (\r) is found.
Stdout(Buffer), use encoding_rs::Encoding;
/// An error happened waiting for the command to finish or converting the stdout/stderr bytes to an UTF-8 string. use serde::{Deserialize, Serialize};
Error(String), use tauri::{api::ipc::CallbackFn, Manager, Runtime, State, Window};
/// Command process terminated.
Terminated(TerminatedPayload), use crate::{
} process::{CommandEvent, TerminatedPayload},
scope::ExecuteArgs,
Shell,
};
fn get_event_buffer(line: Vec<u8>, encoding: EncodingWrapper) -> Result<Buffer, FromUtf8Error> { use super::ChildId;
match encoding {
EncodingWrapper::Text(character_encoding) => match character_encoding { #[derive(Debug, Clone, Serialize)]
Some(encoding) => Ok(Buffer::Text( #[serde(tag = "event", content = "payload")]
encoding.decode_with_bom_removal(&line).0.into(), #[non_exhaustive]
)), enum JSCommandEvent {
None => String::from_utf8(line).map(Buffer::Text), /// Stderr bytes until a newline (\n) or carriage return (\r) is found.
}, Stderr(Buffer),
EncodingWrapper::Raw => Ok(Buffer::Raw(line)), /// Stdout bytes until a newline (\n) or carriage return (\r) is found.
Stdout(Buffer),
/// An error happened waiting for the command to finish or converting the stdout/stderr bytes to an UTF-8 string.
Error(String),
/// Command process terminated.
Terminated(TerminatedPayload),
} }
}
impl JSCommandEvent { fn get_event_buffer(line: Vec<u8>, encoding: EncodingWrapper) -> Result<Buffer, FromUtf8Error> {
pub fn new(event: CommandEvent, encoding: EncodingWrapper) -> Self { match encoding {
match event { EncodingWrapper::Text(character_encoding) => match character_encoding {
CommandEvent::Terminated(payload) => JSCommandEvent::Terminated(payload), Some(encoding) => Ok(Buffer::Text(
CommandEvent::Error(error) => JSCommandEvent::Error(error), encoding.decode_with_bom_removal(&line).0.into(),
CommandEvent::Stderr(line) => get_event_buffer(line, encoding) )),
.map(JSCommandEvent::Stderr) None => String::from_utf8(line).map(Buffer::Text),
.unwrap_or_else(|e| JSCommandEvent::Error(e.to_string())), },
CommandEvent::Stdout(line) => get_event_buffer(line, encoding) EncodingWrapper::Raw => Ok(Buffer::Raw(line)),
.map(JSCommandEvent::Stdout)
.unwrap_or_else(|e| JSCommandEvent::Error(e.to_string())),
} }
} }
}
#[derive(Debug, Clone, Deserialize, Serialize)] impl JSCommandEvent {
#[serde(untagged)] pub fn new(event: CommandEvent, encoding: EncodingWrapper) -> Self {
#[allow(missing_docs)] match event {
pub enum Buffer { CommandEvent::Terminated(payload) => JSCommandEvent::Terminated(payload),
Text(String), CommandEvent::Error(error) => JSCommandEvent::Error(error),
Raw(Vec<u8>), CommandEvent::Stderr(line) => get_event_buffer(line, encoding)
} .map(JSCommandEvent::Stderr)
.unwrap_or_else(|e| JSCommandEvent::Error(e.to_string())),
CommandEvent::Stdout(line) => get_event_buffer(line, encoding)
.map(JSCommandEvent::Stdout)
.unwrap_or_else(|e| JSCommandEvent::Error(e.to_string())),
}
}
}
#[derive(Debug, Copy, Clone)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub enum EncodingWrapper { #[serde(untagged)]
Raw, #[allow(missing_docs)]
Text(Option<&'static Encoding>), pub enum Buffer {
} Text(String),
Raw(Vec<u8>),
}
#[derive(Debug, Clone, Default, Deserialize)] #[derive(Debug, Copy, Clone)]
#[serde(rename_all = "camelCase")] pub enum EncodingWrapper {
pub struct CommandOptions { Raw,
#[serde(default)] Text(Option<&'static Encoding>),
sidecar: bool, }
cwd: Option<PathBuf>,
// by default we don't add any env variables to the spawned process
// but the env is an `Option` so when it's `None` we clear the env.
#[serde(default = "default_env")]
env: Option<HashMap<String, String>>,
// Character encoding for stdout/stderr
encoding: Option<String>,
}
#[allow(clippy::unnecessary_wraps)] #[derive(Debug, Clone, Default, Deserialize)]
fn default_env() -> Option<HashMap<String, String>> { #[serde(rename_all = "camelCase")]
Some(HashMap::default()) pub struct CommandOptions {
} #[serde(default)]
sidecar: bool,
cwd: Option<PathBuf>,
// by default we don't add any env variables to the spawned process
// but the env is an `Option` so when it's `None` we clear the env.
#[serde(default = "default_env")]
env: Option<HashMap<String, String>>,
// Character encoding for stdout/stderr
encoding: Option<String>,
}
#[tauri::command] #[allow(clippy::unnecessary_wraps)]
pub fn execute<R: Runtime>( fn default_env() -> Option<HashMap<String, String>> {
window: Window<R>, Some(HashMap::default())
shell: State<'_, Shell<R>>, }
program: String,
args: ExecuteArgs, #[tauri::command]
on_event_fn: CallbackFn, pub fn execute<R: Runtime>(
options: CommandOptions, window: Window<R>,
) -> crate::Result<ChildId> { shell: State<'_, Shell<R>>,
let mut command = if options.sidecar { program: String,
let program = PathBuf::from(program); args: ExecuteArgs,
let program_as_string = program.display().to_string(); on_event_fn: CallbackFn,
let program_no_ext_as_string = program.with_extension("").display().to_string(); options: CommandOptions,
let configured_sidecar = window ) -> crate::Result<ChildId> {
.config() let mut command = if options.sidecar {
.tauri let program = PathBuf::from(program);
.bundle let program_as_string = program.display().to_string();
.external_bin let program_no_ext_as_string = program.with_extension("").display().to_string();
.as_ref() let configured_sidecar = window
.and_then(|bins| { .config()
bins.iter() .tauri
.find(|b| b == &&program_as_string || b == &&program_no_ext_as_string) .bundle
}) .external_bin
.cloned(); .as_ref()
if let Some(sidecar) = configured_sidecar { .and_then(|bins| {
shell bins.iter()
.scope .find(|b| b == &&program_as_string || b == &&program_no_ext_as_string)
.prepare_sidecar(&program.to_string_lossy(), &sidecar, args)? })
.cloned();
if let Some(sidecar) = configured_sidecar {
shell
.scope
.prepare_sidecar(&program.to_string_lossy(), &sidecar, args)?
} else {
return Err(crate::Error::SidecarNotAllowed(program));
}
} else { } else {
return Err(crate::Error::SidecarNotAllowed(program)); match shell.scope.prepare(&program, args) {
} Ok(cmd) => cmd,
} else { Err(e) => {
match shell.scope.prepare(&program, args) { #[cfg(debug_assertions)]
Ok(cmd) => cmd, eprintln!("{e}");
Err(e) => { return Err(crate::Error::ProgramNotAllowed(PathBuf::from(program)));
#[cfg(debug_assertions)] }
eprintln!("{e}");
return Err(crate::Error::ProgramNotAllowed(PathBuf::from(program)));
} }
};
if let Some(cwd) = options.cwd {
command = command.current_dir(cwd);
} }
}; if let Some(env) = options.env {
if let Some(cwd) = options.cwd { command = command.envs(env);
command = command.current_dir(cwd); } else {
} command = command.env_clear();
if let Some(env) = options.env { }
command = command.envs(env); let encoding = match options.encoding {
} else { Option::None => EncodingWrapper::Text(None),
command = command.env_clear(); Some(encoding) => match encoding.as_str() {
} "raw" => EncodingWrapper::Raw,
let encoding = match options.encoding { _ => {
Option::None => EncodingWrapper::Text(None), if let Some(text_encoding) = Encoding::for_label(encoding.as_bytes()) {
Some(encoding) => match encoding.as_str() { EncodingWrapper::Text(Some(text_encoding))
"raw" => EncodingWrapper::Raw, } else {
_ => { return Err(crate::Error::UnknownEncoding(encoding));
if let Some(text_encoding) = Encoding::for_label(encoding.as_bytes()) { }
EncodingWrapper::Text(Some(text_encoding))
} else {
return Err(crate::Error::UnknownEncoding(encoding));
} }
} },
}, };
};
let (mut rx, child) = command.spawn()?; let (mut rx, child) = command.spawn()?;
let pid = child.pid(); let pid = child.pid();
shell.children.lock().unwrap().insert(pid, child); shell.children.lock().unwrap().insert(pid, child);
let children = shell.children.clone(); let children = shell.children.clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
while let Some(event) = rx.recv().await { while let Some(event) = rx.recv().await {
if matches!(event, crate::process::CommandEvent::Terminated(_)) { if matches!(event, crate::process::CommandEvent::Terminated(_)) {
children.lock().unwrap().remove(&pid); children.lock().unwrap().remove(&pid);
}; };
let js_event = JSCommandEvent::new(event, encoding); let js_event = JSCommandEvent::new(event, encoding);
let js = tauri::api::ipc::format_callback(on_event_fn, &js_event) let js = tauri::api::ipc::format_callback(on_event_fn, &js_event)
.expect("unable to serialize CommandEvent"); .expect("unable to serialize CommandEvent");
let _ = window.eval(js.as_str()); let _ = window.eval(js.as_str());
} }
}); });
Ok(pid) Ok(pid)
}
} }
#[cfg(feature = "allow-stdin-write")]
#[tauri::command] #[tauri::command]
pub fn stdin_write<R: Runtime>( pub fn stdin_write<R: Runtime>(
_window: Window<R>, _window: Window<R>,
@ -188,6 +207,7 @@ pub fn stdin_write<R: Runtime>(
Ok(()) Ok(())
} }
#[cfg(feature = "allow-kill")]
#[tauri::command] #[tauri::command]
pub fn kill<R: Runtime>( pub fn kill<R: Runtime>(
_window: Window<R>, _window: Window<R>,
@ -200,6 +220,7 @@ pub fn kill<R: Runtime>(
Ok(()) Ok(())
} }
#[cfg(feature = "allow-open")]
#[tauri::command] #[tauri::command]
pub fn open<R: Runtime>( pub fn open<R: Runtime>(
_window: Window<R>, _window: Window<R>,

@ -65,9 +65,13 @@ impl<R: Runtime, T: Manager<R>> ShellExt<R> for T {
pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> { pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
Builder::<R, Option<Config>>::new("shell") Builder::<R, Option<Config>>::new("shell")
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
#[cfg(feature = "allow-execute")]
commands::execute, commands::execute,
#[cfg(feature = "allow-stdin-write")]
commands::stdin_write, commands::stdin_write,
#[cfg(feature = "allow-kill")]
commands::kill, commands::kill,
#[cfg(feature = "allow-open")]
commands::open commands::open
]) ])
.setup(|app, api| { .setup(|app, api| {

@ -159,6 +159,7 @@ impl Scope {
} }
/// Validates argument inputs and creates a Tauri sidecar [`Command`]. /// Validates argument inputs and creates a Tauri sidecar [`Command`].
#[allow(dead_code)]
pub fn prepare_sidecar( pub fn prepare_sidecar(
&self, &self,
command_name: &str, command_name: &str,
@ -169,11 +170,13 @@ impl Scope {
} }
/// Validates argument inputs and creates a Tauri [`Command`]. /// Validates argument inputs and creates a Tauri [`Command`].
#[allow(dead_code)]
pub fn prepare(&self, command_name: &str, args: ExecuteArgs) -> Result<Command, Error> { pub fn prepare(&self, command_name: &str, args: ExecuteArgs) -> Result<Command, Error> {
self._prepare(command_name, args, None) self._prepare(command_name, args, None)
} }
/// Validates argument inputs and creates a Tauri [`Command`]. /// Validates argument inputs and creates a Tauri [`Command`].
#[allow(dead_code)]
pub fn _prepare( pub fn _prepare(
&self, &self,
command_name: &str, command_name: &str,

@ -36,3 +36,5 @@ tokio-test = "0.4.2"
native-tls = [ "reqwest/native-tls" ] native-tls = [ "reqwest/native-tls" ]
native-tls-vendored = [ "reqwest/native-tls-vendored" ] native-tls-vendored = [ "reqwest/native-tls-vendored" ]
rustls-tls = [ "reqwest/rustls-tls" ] rustls-tls = [ "reqwest/rustls-tls" ]
allow-check = []
allow-download-and-install = []

@ -1,3 +1,5 @@
#![allow(unused_imports, dead_code)]
use crate::{PendingUpdate, Result, UpdaterExt}; use crate::{PendingUpdate, Result, UpdaterExt};
use http::header; use http::header;
@ -42,6 +44,7 @@ impl<'de> Deserialize<'de> for HeaderMap {
} }
} }
#[cfg(feature = "allow-check")]
#[tauri::command] #[tauri::command]
pub(crate) async fn check<R: Runtime>( pub(crate) async fn check<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
@ -85,6 +88,7 @@ pub(crate) struct DownloadProgress {
content_length: Option<u64>, content_length: Option<u64>,
} }
#[cfg(feature = "allow-download-and-install")]
#[tauri::command] #[tauri::command]
pub(crate) async fn download_and_install<R: Runtime>( pub(crate) async fn download_and_install<R: Runtime>(
_app: AppHandle<R>, _app: AppHandle<R>,

@ -88,7 +88,9 @@ impl Builder {
Ok(()) Ok(())
}) })
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
#[cfg(feature = "allow-check")]
commands::check, commands::check,
#[cfg(feature = "allow-download-and-install")]
commands::download_and_install commands::download_and_install
]) ])
.build() .build()

@ -13,3 +13,51 @@ thiserror.workspace = true
[features] [features]
icon-png = ["tauri/icon-png"] icon-png = ["tauri/icon-png"]
icon-ico = ["tauri/icon-ico"] icon-ico = ["tauri/icon-ico"]
allow-create = []
allow-scale-factor = []
allow-inner-position = []
allow-outer-position = []
allow-inner-size = []
allow-outer-size = []
allow-is-fullscreen = []
allow-is-minimized = []
allow-is-maximized = []
allow-is-decorated = []
allow-is-resizable = []
allow-is-visible = []
allow-title = []
allow-current-monitor = []
allow-primary-monitor = []
allow-available-monitors = []
allow-theme = []
allow-center = []
allow-request-user-attention = []
allow-set-resizable = []
allow-set-title = []
allow-maximize = []
allow-unmaximize = []
allow-minimize = []
allow-unminimize = []
allow-show = []
allow-hide = []
allow-close = []
allow-set-decorations = []
allow-set-shadow = []
allow-set-always-on-top = []
allow-set-content-protected = []
allow-set-size = []
allow-set-min-size = []
allow-set-max-size = []
allow-set-position = []
allow-set-fullscreen = []
allow-set-focus = []
allow-set-skip-taskbar = []
allow-set-cursor-grab = []
allow-set-cursor-visible = []
allow-set-cursor-icon = []
allow-set-cursor-position = []
allow-set-ignore-cursor-events = []
allow-start-dragging = []
allow-print = []
allow-set-icon = []
allow-toggle-maximize = []

@ -1,3 +1,5 @@
#![allow(unused_imports, dead_code)]
use serde::{Deserialize, Serialize, Serializer}; use serde::{Deserialize, Serialize, Serializer};
use tauri::{ use tauri::{
utils::config::WindowConfig, AppHandle, CursorIcon, Icon, Manager, Monitor, PhysicalPosition, utils::config::WindowConfig, AppHandle, CursorIcon, Icon, Manager, Monitor, PhysicalPosition,
@ -57,6 +59,7 @@ impl From<IconDto> for Icon {
} }
} }
#[cfg(feature = "allow-create")]
#[tauri::command] #[tauri::command]
pub fn create<R: Runtime>(app: AppHandle<R>, options: WindowConfig) -> Result<()> { pub fn create<R: Runtime>(app: AppHandle<R>, options: WindowConfig) -> Result<()> {
tauri::window::WindowBuilder::from_config(&app, options).build()?; tauri::window::WindowBuilder::from_config(&app, options).build()?;
@ -70,6 +73,7 @@ fn get_window<R: Runtime>(window: Window<R>, label: Option<String>) -> Result<Wi
} }
} }
#[allow(unused_macros)]
macro_rules! getter { macro_rules! getter {
($cmd: ident, $ret: ty) => { ($cmd: ident, $ret: ty) => {
#[tauri::command] #[tauri::command]
@ -79,6 +83,7 @@ macro_rules! getter {
}; };
} }
#[allow(unused_macros)]
macro_rules! setter { macro_rules! setter {
($cmd: ident) => { ($cmd: ident) => {
#[tauri::command] #[tauri::command]
@ -99,53 +104,99 @@ macro_rules! setter {
}; };
} }
#[cfg(feature = "allow-scale-factor")]
getter!(scale_factor, f64); getter!(scale_factor, f64);
#[cfg(feature = "allow-inner-position")]
getter!(inner_position, PhysicalPosition<i32>); getter!(inner_position, PhysicalPosition<i32>);
#[cfg(feature = "allow-outer-position")]
getter!(outer_position, PhysicalPosition<i32>); getter!(outer_position, PhysicalPosition<i32>);
#[cfg(feature = "allow-inner-size")]
getter!(inner_size, PhysicalSize<u32>); getter!(inner_size, PhysicalSize<u32>);
#[cfg(feature = "allow-outer-size")]
getter!(outer_size, PhysicalSize<u32>); getter!(outer_size, PhysicalSize<u32>);
#[cfg(feature = "allow-is-fullscreen")]
getter!(is_fullscreen, bool); getter!(is_fullscreen, bool);
#[cfg(feature = "allow-is-minimized")]
getter!(is_minimized, bool); getter!(is_minimized, bool);
#[cfg(feature = "allow-is-maximized")]
getter!(is_maximized, bool); getter!(is_maximized, bool);
#[cfg(feature = "allow-is-decorated")]
getter!(is_decorated, bool); getter!(is_decorated, bool);
#[cfg(feature = "allow-is-resizable")]
getter!(is_resizable, bool); getter!(is_resizable, bool);
#[cfg(feature = "allow-is-visible")]
getter!(is_visible, bool); getter!(is_visible, bool);
#[cfg(feature = "allow-title")]
getter!(title, String); getter!(title, String);
#[cfg(feature = "allow-current-monitor")]
getter!(current_monitor, Option<Monitor>); getter!(current_monitor, Option<Monitor>);
#[cfg(feature = "allow-primary-monitor")]
getter!(primary_monitor, Option<Monitor>); getter!(primary_monitor, Option<Monitor>);
#[cfg(feature = "allow-available-monitors")]
getter!(available_monitors, Vec<Monitor>); getter!(available_monitors, Vec<Monitor>);
#[cfg(feature = "allow-theme")]
getter!(theme, Theme); getter!(theme, Theme);
#[cfg(feature = "allow-center")]
setter!(center); setter!(center);
#[cfg(feature = "allow-request-user-attention")]
setter!(request_user_attention, Option<UserAttentionType>); setter!(request_user_attention, Option<UserAttentionType>);
#[cfg(feature = "allow-set-resizable")]
setter!(set_resizable, bool); setter!(set_resizable, bool);
#[cfg(feature = "allow-set-title")]
setter!(set_title, &str); setter!(set_title, &str);
#[cfg(feature = "allow-maximize")]
setter!(maximize); setter!(maximize);
#[cfg(feature = "allow-unmaximize")]
setter!(unmaximize); setter!(unmaximize);
#[cfg(feature = "allow-minimize")]
setter!(minimize); setter!(minimize);
#[cfg(feature = "allow-unminimize")]
setter!(unminimize); setter!(unminimize);
#[cfg(feature = "allow-show")]
setter!(show); setter!(show);
#[cfg(feature = "allow-hide")]
setter!(hide); setter!(hide);
#[cfg(feature = "allow-close")]
setter!(close); setter!(close);
#[cfg(feature = "allow-set-decorations")]
setter!(set_decorations, bool); setter!(set_decorations, bool);
#[cfg(feature = "allow-set-shadow")]
setter!(set_shadow, bool); setter!(set_shadow, bool);
#[cfg(feature = "allow-set-always-on-top")]
setter!(set_always_on_top, bool); setter!(set_always_on_top, bool);
#[cfg(feature = "allow-set-content-protected")]
setter!(set_content_protected, bool); setter!(set_content_protected, bool);
#[cfg(feature = "allow-set-size")]
setter!(set_size, Size); setter!(set_size, Size);
#[cfg(feature = "allow-set-min-size")]
setter!(set_min_size, Option<Size>); setter!(set_min_size, Option<Size>);
#[cfg(feature = "allow-set-max-size")]
setter!(set_max_size, Option<Size>); setter!(set_max_size, Option<Size>);
#[cfg(feature = "allow-set-position")]
setter!(set_position, Position); setter!(set_position, Position);
#[cfg(feature = "allow-set-fullscreen")]
setter!(set_fullscreen, bool); setter!(set_fullscreen, bool);
#[cfg(feature = "allow-set-focus")]
setter!(set_focus); setter!(set_focus);
#[cfg(feature = "allow-set-skip-taskbar")]
setter!(set_skip_taskbar, bool); setter!(set_skip_taskbar, bool);
#[cfg(feature = "allow-set-cursor-grab")]
setter!(set_cursor_grab, bool); setter!(set_cursor_grab, bool);
#[cfg(feature = "allow-set-cursor-visible")]
setter!(set_cursor_visible, bool); setter!(set_cursor_visible, bool);
#[cfg(feature = "allow-set-cursor-icon")]
setter!(set_cursor_icon, CursorIcon); setter!(set_cursor_icon, CursorIcon);
#[cfg(feature = "allow-set-cursor-position")]
setter!(set_cursor_position, Position); setter!(set_cursor_position, Position);
#[cfg(feature = "allow-set-ignore-cursor-events")]
setter!(set_ignore_cursor_events, bool); setter!(set_ignore_cursor_events, bool);
#[cfg(feature = "allow-start-dragging")]
setter!(start_dragging); setter!(start_dragging);
#[cfg(feature = "allow-print")]
setter!(print); setter!(print);
#[cfg(feature = "allow-set-icon")]
#[tauri::command] #[tauri::command]
pub fn set_icon<R: Runtime>( pub fn set_icon<R: Runtime>(
window: Window<R>, window: Window<R>,
@ -157,6 +208,7 @@ pub fn set_icon<R: Runtime>(
.map_err(Into::into) .map_err(Into::into)
} }
#[cfg(feature = "allow-toggle-maximize")]
#[tauri::command] #[tauri::command]
pub fn toggle_maximize<R: Runtime>(window: Window<R>, label: Option<String>) -> Result<()> { pub fn toggle_maximize<R: Runtime>(window: Window<R>, label: Option<String>) -> Result<()> {
let window = get_window(window, label)?; let window = get_window(window, label)?;
@ -167,6 +219,7 @@ pub fn toggle_maximize<R: Runtime>(window: Window<R>, label: Option<String>) ->
Ok(()) Ok(())
} }
#[cfg(feature = "allow-toggle-maximize")]
#[tauri::command] #[tauri::command]
pub fn internal_toggle_maximize<R: Runtime>( pub fn internal_toggle_maximize<R: Runtime>(
window: Window<R>, window: Window<R>,
@ -182,6 +235,7 @@ pub fn internal_toggle_maximize<R: Runtime>(
Ok(()) Ok(())
} }
#[cfg(debug_assertions)]
#[tauri::command] #[tauri::command]
pub fn internal_toggle_devtools<R: Runtime>( pub fn internal_toggle_devtools<R: Runtime>(
window: Window<R>, window: Window<R>,

@ -8,57 +8,107 @@ mod commands;
pub fn init<R: Runtime>() -> TauriPlugin<R> { pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("window") Builder::new("window")
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
#[cfg(feature = "allow-create")]
commands::create, commands::create,
// getters // getters
#[cfg(feature = "allow-scale-factor")]
commands::scale_factor, commands::scale_factor,
#[cfg(feature = "allow-inner-position")]
commands::inner_position, commands::inner_position,
#[cfg(feature = "allow-outer-position")]
commands::outer_position, commands::outer_position,
#[cfg(feature = "allow-inner-size")]
commands::inner_size, commands::inner_size,
#[cfg(feature = "allow-outer-size")]
commands::outer_size, commands::outer_size,
#[cfg(feature = "allow-is-fullscreen")]
commands::is_fullscreen, commands::is_fullscreen,
#[cfg(feature = "allow-is-minimized")]
commands::is_minimized, commands::is_minimized,
#[cfg(feature = "allow-is-maximized")]
commands::is_maximized, commands::is_maximized,
#[cfg(feature = "allow-is-decorated")]
commands::is_decorated, commands::is_decorated,
#[cfg(feature = "allow-is-resizable")]
commands::is_resizable, commands::is_resizable,
#[cfg(feature = "allow-is-visible")]
commands::is_visible, commands::is_visible,
#[cfg(feature = "allow-title")]
commands::title, commands::title,
#[cfg(feature = "allow-current-monitor")]
commands::current_monitor, commands::current_monitor,
#[cfg(feature = "allow-primary-monitor")]
commands::primary_monitor, commands::primary_monitor,
#[cfg(feature = "allow-available-monitors")]
commands::available_monitors, commands::available_monitors,
#[cfg(feature = "allow-theme")]
commands::theme, commands::theme,
// setters // setters
#[cfg(feature = "allow-center")]
commands::center, commands::center,
#[cfg(feature = "allow-request-user-attention")]
commands::request_user_attention, commands::request_user_attention,
#[cfg(feature = "allow-set-resizable")]
commands::set_resizable, commands::set_resizable,
#[cfg(feature = "allow-set-title")]
commands::set_title, commands::set_title,
#[cfg(feature = "allow-maximize")]
commands::maximize, commands::maximize,
#[cfg(feature = "allow-unmaximize")]
commands::unmaximize, commands::unmaximize,
#[cfg(feature = "allow-minimize")]
commands::minimize, commands::minimize,
#[cfg(feature = "allow-unminimize")]
commands::unminimize, commands::unminimize,
#[cfg(feature = "allow-show")]
commands::show, commands::show,
#[cfg(feature = "allow-hide")]
commands::hide, commands::hide,
#[cfg(feature = "allow-close")]
commands::close, commands::close,
#[cfg(feature = "allow-set-decorations")]
commands::set_decorations, commands::set_decorations,
#[cfg(feature = "allow-set-shadow")]
commands::set_shadow, commands::set_shadow,
#[cfg(feature = "allow-set-always-on-top")]
commands::set_always_on_top, commands::set_always_on_top,
#[cfg(feature = "allow-set-content-protected")]
commands::set_content_protected, commands::set_content_protected,
#[cfg(feature = "allow-set-size")]
commands::set_size, commands::set_size,
#[cfg(feature = "allow-set-min-size")]
commands::set_min_size, commands::set_min_size,
#[cfg(feature = "allow-set-max-size")]
commands::set_max_size, commands::set_max_size,
#[cfg(feature = "allow-set-position")]
commands::set_position, commands::set_position,
#[cfg(feature = "allow-set-fullscreen")]
commands::set_fullscreen, commands::set_fullscreen,
#[cfg(feature = "allow-set-focus")]
commands::set_focus, commands::set_focus,
#[cfg(feature = "allow-set-skip-taskbar")]
commands::set_skip_taskbar, commands::set_skip_taskbar,
#[cfg(feature = "allow-set-cursor-grab")]
commands::set_cursor_grab, commands::set_cursor_grab,
#[cfg(feature = "allow-set-cursor-visible")]
commands::set_cursor_visible, commands::set_cursor_visible,
#[cfg(feature = "allow-set-cursor-icon")]
commands::set_cursor_icon, commands::set_cursor_icon,
#[cfg(feature = "allow-set-cursor-position")]
commands::set_cursor_position, commands::set_cursor_position,
#[cfg(feature = "allow-set-ignore-cursor-events")]
commands::set_ignore_cursor_events, commands::set_ignore_cursor_events,
#[cfg(feature = "allow-start-dragging")]
commands::start_dragging, commands::start_dragging,
#[cfg(feature = "allow-print")]
commands::print, commands::print,
#[cfg(feature = "allow-set-icon")]
commands::set_icon, commands::set_icon,
#[cfg(feature = "allow-toggle-maximize")]
commands::toggle_maximize, commands::toggle_maximize,
#[cfg(feature = "allow-toggle-maximize")]
commands::internal_toggle_maximize, commands::internal_toggle_maximize,
#[cfg(debug_assertions)]
commands::internal_toggle_devtools, commands::internal_toggle_devtools,
]) ])
.build() .build()

Loading…
Cancel
Save