├── examples └── tauri-app │ ├── src │ ├── vite-env.d.ts │ ├── main.tsx │ ├── App.tsx │ └── styles.css │ ├── src-tauri │ ├── build.rs │ ├── .gitignore │ ├── icons │ │ ├── icon.ico │ │ ├── icon.png │ │ ├── 32x32.png │ │ ├── icon.icns │ │ ├── 128x128.png │ │ └── 128x128@2x.png │ ├── gen │ │ └── schemas │ │ │ ├── capabilities.json │ │ │ └── acl-manifests.json │ ├── Cargo.toml │ ├── capabilities │ │ └── default.json │ ├── src │ │ └── main.rs │ └── tauri.conf.json │ ├── .vscode │ └── extensions.json │ ├── tsconfig.node.json │ ├── .gitignore │ ├── index.html │ ├── README.md │ ├── vite.config.ts │ ├── tsconfig.json │ ├── package.json │ └── public │ └── tauri.svg ├── wheeee.gif ├── .github └── FUNDING.yml ├── guest-js └── index.ts ├── .gitignore ├── tsconfig.json ├── src ├── dconf.rs ├── commands.rs ├── js │ ├── titlebar.js │ ├── linux-controls.js │ └── controls.js ├── lib.rs └── traffic.rs ├── permissions ├── autogenerated │ ├── commands │ │ └── show_snap_overlay.toml │ └── reference.md └── schemas │ └── schema.json ├── workflows ├── audit.yml ├── test.yml └── clippy.yml ├── rollup.config.js ├── package.json ├── Cargo.toml ├── LICENSE ├── README.md └── yarn.lock /examples/tauri-app/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/tauri-app/src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /wheeee.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clearlysid/tauri-plugin-decorum/HEAD/wheeee.gif -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [clearlysid] 4 | -------------------------------------------------------------------------------- /examples/tauri-app/src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | -------------------------------------------------------------------------------- /examples/tauri-app/src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clearlysid/tauri-plugin-decorum/HEAD/examples/tauri-app/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /examples/tauri-app/src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clearlysid/tauri-plugin-decorum/HEAD/examples/tauri-app/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /examples/tauri-app/src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clearlysid/tauri-plugin-decorum/HEAD/examples/tauri-app/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /examples/tauri-app/src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clearlysid/tauri-plugin-decorum/HEAD/examples/tauri-app/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /examples/tauri-app/src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clearlysid/tauri-plugin-decorum/HEAD/examples/tauri-app/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /examples/tauri-app/src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clearlysid/tauri-plugin-decorum/HEAD/examples/tauri-app/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /guest-js/index.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/core"; 2 | 3 | export async function show_snap_overlay() { 4 | await invoke("plugin:decorum|show_snap_overlay"); 5 | } 6 | -------------------------------------------------------------------------------- /examples/tauri-app/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "svelte.svelte-vscode", 4 | "tauri-apps.tauri-vscode", 5 | "rust-lang.rust-analyzer" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /examples/tauri-app/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vs 2 | .DS_Store 3 | .Thumbs.db 4 | *.sublime* 5 | .idea/ 6 | debug.log 7 | package-lock.json 8 | .vscode/settings.json 9 | yarn.lock 10 | 11 | /.tauri 12 | /target 13 | Cargo.lock 14 | node_modules/ 15 | 16 | dist-js 17 | dist 18 | 19 | examples/tauri-app/pnpm-lock.yaml 20 | -------------------------------------------------------------------------------- /examples/tauri-app/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | import "./styles.css"; 5 | 6 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 7 | 8 | 9 | , 10 | ); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "module": "esnext", 5 | "moduleResolution": "bundler", 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noUnusedLocals": true, 9 | "noImplicitAny": true, 10 | "noEmit": true 11 | }, 12 | "include": ["guest-js/*.ts"], 13 | "exclude": ["dist-js", "node_modules"] 14 | } 15 | -------------------------------------------------------------------------------- /examples/tauri-app/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/dconf.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | pub fn read(path: &str) -> Result { 4 | if let Ok(output) = Command::new("dconf").args(["read", path]).output() { 5 | Ok(String::from_utf8_lossy(&output.stdout) 6 | .to_string() 7 | .replace('\'', "") 8 | .replace('"', "") 9 | .replace('\n', "")) 10 | } else { 11 | Err(()) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/tauri-app/src/App.tsx: -------------------------------------------------------------------------------- 1 | function App() { 2 | return ( 3 |
4 |

Welcome to Tauri!

5 | 6 |
7 | 8 | Tauri logo 9 | 10 |
11 |

Click on the Tauri logo to learn more.

12 |
13 | ); 14 | } 15 | 16 | export default App; -------------------------------------------------------------------------------- /examples/tauri-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Tauri + React + TS 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/tauri-app/README.md: -------------------------------------------------------------------------------- 1 | # Svelte + Vite 2 | 3 | This template should help get you started developing with Tauri and Svelte in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer). 8 | 9 | -------------------------------------------------------------------------------- /permissions/autogenerated/commands/show_snap_overlay.toml: -------------------------------------------------------------------------------- 1 | # Automatically generated - DO NOT EDIT! 2 | 3 | "$schema" = "../../schemas/schema.json" 4 | 5 | [[permission]] 6 | identifier = "allow-show-snap-overlay" 7 | description = "Enables the show_snap_overlay command without any pre-configured scope." 8 | commands.allow = ["show_snap_overlay"] 9 | 10 | [[permission]] 11 | identifier = "deny-show-snap-overlay" 12 | description = "Denies the show_snap_overlay command without any pre-configured scope." 13 | commands.deny = ["show_snap_overlay"] 14 | -------------------------------------------------------------------------------- /permissions/autogenerated/reference.md: -------------------------------------------------------------------------------- 1 | 2 | ## Permission Table 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 22 | 23 | 24 | 25 | 30 | 35 | 36 |
IdentifierDescription
13 | 14 | `decorum:allow-show-snap-overlay` 15 | 16 | 18 | 19 | Enables the show_snap_overlay command without any pre-configured scope. 20 | 21 |
26 | 27 | `decorum:deny-show-snap-overlay` 28 | 29 | 31 | 32 | Denies the show_snap_overlay command without any pre-configured scope. 33 | 34 |
37 | -------------------------------------------------------------------------------- /src/commands.rs: -------------------------------------------------------------------------------- 1 | use tauri; 2 | 3 | #[tauri::command] 4 | pub async fn show_snap_overlay() { 5 | #[cfg(target_os = "windows")] 6 | { 7 | use enigo::{Enigo, Key, KeyboardControllable}; 8 | 9 | // press win + z using enigo 10 | let mut enigo = Enigo::new(); 11 | enigo.key_down(Key::Meta); 12 | enigo.key_click(Key::Layout('z')); 13 | enigo.key_up(Key::Meta); 14 | 15 | // Wait 50 ms 16 | std::thread::sleep(std::time::Duration::from_millis(50)); 17 | 18 | // Press Alt to hide the ugly numbers 19 | enigo.key_click(Key::Alt); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/tauri-app/src-tauri/gen/schemas/capabilities.json: -------------------------------------------------------------------------------- 1 | {"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:app:default","core:path:default","core:menu:default","core:tray:default","core:image:default","core:event:default","core:window:default","core:resources:default","core:window:allow-close","core:window:allow-center","core:window:allow-minimize","core:window:allow-maximize","core:window:allow-set-size","core:window:allow-set-focus","core:window:allow-is-maximized","core:window:allow-start-dragging","core:window:allow-toggle-maximize","decorum:allow-show-snap-overlay"]}} -------------------------------------------------------------------------------- /examples/tauri-app/src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tauri-app" 3 | version = "0.0.0" 4 | description = "A Tauri App" 5 | authors = ["you"] 6 | license = "" 7 | repository = "" 8 | edition = "2021" 9 | rust-version = "1.70" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [build-dependencies] 14 | tauri-build = { version = "2.0.0-rc", default-features = false , features = [] } 15 | 16 | [dependencies] 17 | tauri = { version = "2.0.0-rc", features = [] } 18 | tauri-plugin-decorum = { path = "../../../" } 19 | serde = { version = "1", features = ["derive"] } 20 | serde_json = "1" 21 | -------------------------------------------------------------------------------- /examples/tauri-app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig(async () => ({ 6 | plugins: [react()], 7 | 8 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` 9 | // 10 | // 1. prevent vite from obscuring rust errors 11 | clearScreen: false, 12 | // 2. tauri expects a fixed port, fail if that port is not available 13 | server: { 14 | port: 1420, 15 | strictPort: true, 16 | watch: { 17 | // 3. tell vite to ignore watching `src-tauri` 18 | ignored: ["**/src-tauri/**"], 19 | }, 20 | }, 21 | })); 22 | -------------------------------------------------------------------------------- /examples/tauri-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } -------------------------------------------------------------------------------- /examples/tauri-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tauri-app", 3 | "private": false, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview", 10 | "tauri": "tauri" 11 | }, 12 | "dependencies": { 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0", 15 | "@tauri-apps/api": "^2.0.0-beta.15", 16 | "tauri-plugin-decorum-api": "link:../../" 17 | }, 18 | "devDependencies": { 19 | "@types/react": "^18.2.15", 20 | "@types/react-dom": "^18.2.7", 21 | "@vitejs/plugin-react": "^4.2.1", 22 | "typescript": "^5.0.2", 23 | "vite": "^5.0.0", 24 | "@tauri-apps/cli": ">=2.0.0-beta.22" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Audit 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | push: 7 | branches: 8 | - main 9 | paths: 10 | - ".github/workflows/audit.yml" 11 | - "**/Cargo.lock" 12 | - "**/Cargo.toml" 13 | pull_request: 14 | branches: 15 | - main 16 | paths: 17 | - ".github/workflows/audit.yml" 18 | - "**/Cargo.lock" 19 | - "**/Cargo.toml" 20 | 21 | concurrency: 22 | group: ${{ github.workflow }}-${{ github.ref }} 23 | cancel-in-progress: true 24 | 25 | jobs: 26 | audit: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v4 30 | - uses: rustsec/audit-check@v1 31 | with: 32 | token: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs' 2 | import { join } from 'path' 3 | import { cwd } from 'process' 4 | import typescript from '@rollup/plugin-typescript' 5 | 6 | const pkg = JSON.parse(readFileSync(join(cwd(), 'package.json'), 'utf8')) 7 | 8 | export default { 9 | input: 'guest-js/index.ts', 10 | output: [ 11 | { 12 | file: pkg.exports.import, 13 | format: 'esm' 14 | }, 15 | { 16 | file: pkg.exports.require, 17 | format: 'cjs' 18 | } 19 | ], 20 | plugins: [ 21 | typescript({ 22 | declaration: true, 23 | declarationDir: `./${pkg.exports.import.split('/')[0]}` 24 | }) 25 | ], 26 | external: [ 27 | /^@tauri-apps\/api/, 28 | ...Object.keys(pkg.dependencies || {}), 29 | ...Object.keys(pkg.peerDependencies || {}) 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | test: 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | platform: [ubuntu-latest, macos-latest, windows-latest] 21 | 22 | runs-on: ${{ matrix.platform }} 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: dtolnay/rust-toolchain@stable 27 | - name: install webkit2gtk 28 | if: matrix.platform == 'ubuntu-latest' 29 | run: | 30 | sudo apt-get update 31 | sudo apt-get install -y webkit2gtk-4.1 32 | - uses: Swatinem/rust-cache@v2 33 | - run: cargo test --all-targets --all-features -- -D warnings 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tauri-plugin-decorum-api", 3 | "version": "1.1.1", 4 | "author": "Siddharth ", 5 | "description": "", 6 | "type": "module", 7 | "types": "./dist-js/index.d.ts", 8 | "main": "./dist-js/index.cjs", 9 | "module": "./dist-js/index.js", 10 | "exports": { 11 | "types": "./dist-js/index.d.ts", 12 | "import": "./dist-js/index.js", 13 | "require": "./dist-js/index.cjs" 14 | }, 15 | "files": [ 16 | "dist-js", 17 | "README.md" 18 | ], 19 | "scripts": { 20 | "build": "rollup -c", 21 | "prepublishOnly": "yarn build", 22 | "pretest": "yarn build" 23 | }, 24 | "dependencies": { 25 | "@tauri-apps/api": ">=2.0.0-beta.24" 26 | }, 27 | "devDependencies": { 28 | "@rollup/plugin-typescript": "^11.1.6", 29 | "rollup": "^4.9.6", 30 | "typescript": "^5.3.3", 31 | "tslib": "^2.6.2" 32 | } 33 | } -------------------------------------------------------------------------------- /examples/tauri-app/src-tauri/capabilities/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../gen/schemas/desktop-schema.json", 3 | "identifier": "default", 4 | "description": "Capability for the main window", 5 | "windows": [ 6 | "main" 7 | ], 8 | "permissions": [ 9 | "core:app:default", 10 | "core:path:default", 11 | "core:menu:default", 12 | "core:tray:default", 13 | "core:image:default", 14 | "core:event:default", 15 | "core:window:default", 16 | "core:resources:default", 17 | "core:window:allow-close", 18 | "core:window:allow-center", 19 | "core:window:allow-minimize", 20 | "core:window:allow-maximize", 21 | "core:window:allow-set-size", 22 | "core:window:allow-set-focus", 23 | "core:window:allow-is-maximized", 24 | "core:window:allow-start-dragging", 25 | "core:window:allow-toggle-maximize", 26 | "decorum:allow-show-snap-overlay" 27 | ] 28 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tauri-plugin-decorum" 3 | version = "1.1.1" 4 | authors = ["Siddharth "] 5 | description = "Opnionated window decoration controls for Tauri apps." 6 | edition = "2021" 7 | rust-version = "1.70" 8 | exclude = ["/examples", "/webview-dist", "/webview-src", "/node_modules"] 9 | links = "tauri-plugin-decorum" 10 | license = "MIT" 11 | homepage = "https://github.com/clearlysid/tauri-plugin-decorum" 12 | repository = "https://github.com/clearlysid/tauri-plugin-decorum" 13 | 14 | [dependencies] 15 | tauri = { version = "2.0.0-rc" } 16 | serde = "1.0" 17 | anyhow = "1.0" 18 | 19 | [target.'cfg(target_os = "macos")'.dependencies] 20 | rand = "^0.8" 21 | cocoa = "0.25" 22 | objc = "0.2" 23 | 24 | [target.'cfg(target_os = "windows")'.dependencies] 25 | enigo = "0.1.3" 26 | 27 | [target.'cfg(target_os = "linux")'.dependencies] 28 | linicon = "2.3.0" 29 | 30 | [build-dependencies] 31 | tauri-plugin = { version = "2.0.0-rc", features = ["build"] } 32 | -------------------------------------------------------------------------------- /examples/tauri-app/src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | use tauri::Manager; 5 | use tauri_plugin_decorum::WebviewWindowExt; 6 | 7 | fn main() { 8 | tauri::Builder::default() 9 | .plugin(tauri_plugin_decorum::init()) 10 | .setup(|app| { 11 | // Create a custom titlebar for main window 12 | // On Windows this will hide decoration and render custom window controls 13 | // On macOS it expects a hiddenTitle: true and titleBarStyle: overlay 14 | let main_window = app.get_webview_window("main").unwrap(); 15 | main_window.create_overlay_titlebar().unwrap(); 16 | 17 | #[cfg(target_os = "macos")] 18 | main_window.set_traffic_lights_inset(16.0, 20.0).unwrap(); 19 | Ok(()) 20 | }) 21 | .run(tauri::generate_context!()) 22 | .expect("error while running tauri application"); 23 | } 24 | -------------------------------------------------------------------------------- /examples/tauri-app/src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "productName": "tauri-app", 3 | "version": "0.0.0", 4 | "identifier": "com.tauritempplugin.dev", 5 | "build": { 6 | "beforeDevCommand": "yarn dev", 7 | "beforeBuildCommand": "yarn build", 8 | "devUrl": "http://localhost:1420", 9 | "frontendDist": "../dist" 10 | }, 11 | "app": { 12 | "withGlobalTauri": true, 13 | "security": { 14 | "csp": null 15 | }, 16 | "windows": [ 17 | { 18 | "fullscreen": false, 19 | "resizable": true, 20 | "title": "tauri-app", 21 | "width": 600, 22 | "height": 400, 23 | "titleBarStyle": "Overlay", 24 | "hiddenTitle": true, 25 | "decorations": false 26 | } 27 | ] 28 | }, 29 | "bundle": { 30 | "active": true, 31 | "targets": "all", 32 | "icon": [ 33 | "icons/32x32.png", 34 | "icons/128x128.png", 35 | "icons/128x128@2x.png", 36 | "icons/icon.icns", 37 | "icons/icon.ico" 38 | ] 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Siddharth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/js/titlebar.js: -------------------------------------------------------------------------------- 1 | 2 | document.addEventListener("DOMContentLoaded", () => { 3 | let tbEl = document.querySelector("[data-tauri-decorum-tb]"); 4 | 5 | if (!tbEl) { 6 | console.log( 7 | "DECORUM: Element with data-tauri-decorum-tb not found. Creating one.", 8 | ); 9 | 10 | // Create titlebar element 11 | tbEl = document.createElement("div"); 12 | tbEl.setAttribute("data-tauri-decorum-tb", ""); 13 | tbEl.setAttribute("role", "group"); 14 | tbEl.setAttribute("lang", "en"); 15 | tbEl.setAttribute("aria-label", "Window controls"); 16 | tbEl.style.top = 0; 17 | tbEl.style.left = 0; 18 | tbEl.style.zIndex = 100; 19 | tbEl.style.width = "100%"; 20 | tbEl.style.height = "32px"; 21 | tbEl.style.display = "flex"; 22 | tbEl.style.position = "fixed"; 23 | tbEl.style.alignItems = "end"; 24 | tbEl.style.justifyContent = "end"; 25 | tbEl.style.backgroundColor = "transparent"; 26 | 27 | // Create draggable area 28 | const drag = document.createElement("div"); 29 | drag.style.width = "100%"; 30 | drag.style.height = "100%"; 31 | drag.style.background = "transparent"; 32 | drag.setAttribute("data-tauri-drag-region", ""); 33 | tbEl.appendChild(drag); 34 | 35 | // add tbEl to the body 36 | document.body.prepend(tbEl); 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - ".github/workflows/check.yml" 9 | - "**/*.rs" 10 | - "**/Cargo.toml" 11 | pull_request: 12 | branches: 13 | - main 14 | paths: 15 | - ".github/workflows/check.yml" 16 | - "**/*.rs" 17 | - "**/Cargo.toml" 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | fmt: 25 | runs-on: ubuntu-latest 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: dtolnay/rust-toolchain@stable 30 | with: 31 | components: rustfmt 32 | - run: cargo fmt --all -- --check 33 | 34 | clippy: 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | platform: [ubuntu-latest, macos-latest, windows-latest] 39 | 40 | runs-on: ${{ matrix.platform }} 41 | 42 | steps: 43 | - uses: actions/checkout@v4 44 | - uses: dtolnay/rust-toolchain@stable 45 | with: 46 | components: clippy 47 | - name: install webkit2gtk 48 | if: matrix.platform == 'ubuntu-latest' 49 | run: | 50 | sudo apt-get update 51 | sudo apt-get install -y webkit2gtk-4.1 52 | - uses: Swatinem/rust-cache@v2 53 | - run: cargo clippy --all-targets --all-features -- -D warnings 54 | -------------------------------------------------------------------------------- /examples/tauri-app/src/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color: #222222; 8 | background-color: #f6f6f6; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-text-size-adjust: 100%; 15 | } 16 | 17 | .container { 18 | margin: 0; 19 | height: 100vh; 20 | display: flex; 21 | text-align: center; 22 | flex-direction: column; 23 | justify-content: center; 24 | } 25 | 26 | .logo { 27 | height: 6em; 28 | padding: 1.5em; 29 | will-change: filter; 30 | transition: 0.75s; 31 | } 32 | 33 | .logo.tauri:hover { 34 | filter: drop-shadow(0 0 2em #24c8db); 35 | } 36 | 37 | .row { 38 | display: flex; 39 | justify-content: center; 40 | } 41 | 42 | a { 43 | font-weight: 500; 44 | color: #646cff; 45 | text-decoration: inherit; 46 | } 47 | 48 | a:hover { 49 | color: #535bf2; 50 | } 51 | 52 | h1 { 53 | text-align: center; 54 | } 55 | 56 | input, 57 | button { 58 | border-radius: 8px; 59 | border: 1px solid transparent; 60 | padding: 0.6em 1.2em; 61 | font-size: 1em; 62 | font-weight: 500; 63 | font-family: inherit; 64 | color: #0f0f0f; 65 | background-color: #ffffff; 66 | transition: border-color 0.25s; 67 | box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); 68 | } 69 | 70 | button { 71 | cursor: pointer; 72 | } 73 | 74 | button:hover { 75 | border-color: #396cd8; 76 | } 77 | button:active { 78 | border-color: #396cd8; 79 | background-color: #e8e8e8; 80 | } 81 | 82 | input, 83 | button { 84 | outline: none; 85 | } 86 | 87 | #greet-input { 88 | margin-right: 5px; 89 | } 90 | 91 | @media (prefers-color-scheme: dark) { 92 | :root { 93 | color: #f6f6f6; 94 | background-image: linear-gradient(180deg, #350042 0%, #1f003b 100%); 95 | background-size: cover; 96 | } 97 | 98 | body { 99 | height: 100vh; 100 | overscroll-behavior: none; 101 | overflow: hidden; 102 | } 103 | 104 | a:hover { 105 | color: #24c8db; 106 | } 107 | 108 | input, 109 | button { 110 | color: #ffffff; 111 | background-color: #0f0f0f98; 112 | } 113 | button:active { 114 | background-color: #0f0f0f69; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /examples/tauri-app/public/tauri.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tauri-plugin-decorum 2 | 3 | Being a designer, I'm _very_ particular about window decorations. This Tauri (v2) plugin is an opinionated take on titlebars that my gripes with the default ones. Features: 4 | 1. retain native features, like Windows Snap Layout. 5 | 2. blend into your app's UI better with transparency and overlay controls. 6 | 3. inset macOS traffic lights that are often misaligned with other window contents. 7 | 8 | ![demo](./wheeee.gif) 9 | 10 | ## Installation and Usage 11 | 12 | For a full example app that uses this plugin, check out [examples/tauri-app](examples/tauri-app/). 13 | 14 | ### install the plugin 15 | 16 | ```bash 17 | cargo add tauri-plugin-decorum 18 | ``` 19 | 20 | ### set permissions 21 | 22 | You'll need to set these for your window in `src-tauri/capabilities/default.json` 23 | 24 | ``` 25 | "core:window:allow-close", 26 | "core:window:allow-center", 27 | "core:window:allow-minimize", 28 | "core:window:allow-maximize", 29 | "core:window:allow-set-size", 30 | "core:window:allow-set-focus", 31 | "core:window:allow-is-maximized", 32 | "core:window:allow-start-dragging", 33 | "core:window:allow-toggle-maximize", 34 | "decorum:allow-show-snap-overlay", 35 | ``` 36 | 37 | And ensure the `withGlobalTauri` in your `tauri.conf.json` is set to `true`. 38 | 39 | \*there's probably a better way to handle plugin permissions that I haven't found yet. if you have, pls lmk! 40 | 41 | 42 | ### usage in tauri: 43 | 44 | ```rust 45 | use tauri::Manager; 46 | 47 | use tauri_plugin_decorum::WebviewWindowExt; // adds helper methods to WebviewWindow 48 | 49 | fn main() { 50 | tauri::Builder::default() 51 | .plugin(tauri_plugin_decorum::init()) // initialize the decorum plugin 52 | .setup(|app| { 53 | // Create a custom titlebar for main window 54 | // On Windows this hides decoration and creates custom window controls 55 | // On macOS it needs hiddenTitle: true and titleBarStyle: overlay 56 | let main_window = app.get_webview_window("main").unwrap(); 57 | main_window.create_overlay_titlebar().unwrap(); 58 | 59 | // Some macOS-specific helpers 60 | #[cfg(target_os = "macos")] { 61 | // Set a custom inset to the traffic lights 62 | main_window.set_traffic_lights_inset(12.0, 16.0).unwrap(); 63 | 64 | // Make window transparent without privateApi 65 | main_window.make_transparent().unwrap() 66 | 67 | // Set window level 68 | // NSWindowLevel: https://developer.apple.com/documentation/appkit/nswindowlevel 69 | main_window.set_window_level(25).unwrap() 70 | } 71 | 72 | Ok(()) 73 | }) 74 | .run(tauri::generate_context!()) 75 | .expect("error while running tauri application"); 76 | } 77 | ``` 78 | 79 | ### custom buttons with css: 80 | 81 | If you want to style the window controls yourself, you can use one of the following class-names to do so: 82 | 83 | ```css 84 | button.decorum-tb-btn, 85 | button#decorum-tb-minimize, 86 | button#decorum-tb-maximize, 87 | button#decorum-tb-close, 88 | div[data-tauri-decorum-tb], {} 89 | ``` 90 | 91 | ## Development Guide 92 | 93 | PRs and issues welcome! Here's a short primer to get you started with development on this: 94 | 95 | 1. Ensure you have all the [Tauri prerequisites](https://beta.tauri.app/start/prerequisites/) set up 96 | 2. Clone this repo 97 | 3. Use the [example app](examples/tauri-app) as a test bed with `yarn tauri dev` 98 | 99 | ## Roadmap 100 | 101 | ~~There's some missing features I'd still like to add, all documented on the [Issues page](https://github.com/clearlysid/tauri-plugin-decorum/issues).~~ 102 | 103 | All the features I wanted are now added by me or a community member — thank you so much for your contributions! 🥳 104 | 105 | The project mostly in maintainance mode now — no breaking API changes, other than architecture improvements and bugfixes. PRs are always welcome! I'll help merge them as quick as I can. In the long run I hope the core team incorporates all these within Tauri and I look forward to making this plugin obsolete. 106 | 107 | Meanwhile, I hope you find it useful. Happy building! 🥂 108 | -------------------------------------------------------------------------------- /src/js/linux-controls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} selector 3 | * @returns {Promise} 4 | */ 5 | function waitForElm(selector) { 6 | return new Promise((resolve) => { 7 | if (document.querySelector(selector)) { 8 | return resolve(document.querySelector(selector)); 9 | } 10 | 11 | const observer = new MutationObserver((mutations) => { 12 | if (document.querySelector(selector)) { 13 | observer.disconnect(); 14 | resolve(document.querySelector(selector)); 15 | } 16 | }); 17 | 18 | // If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336 19 | observer.observe(document.body, { 20 | childList: true, 21 | subtree: true, 22 | }); 23 | }); 24 | } 25 | 26 | document.addEventListener("DOMContentLoaded", () => { 27 | // All of this tags will be replaced by the found system theme icons 28 | const windowCloseSvg = `@win-close`; 29 | const windowMinimizeSvg = `@win-minimize`; 30 | const windowMaximizeSvg = `@win-maximize`; 31 | const windowRestoreSvg = `@win-restore`; 32 | 33 | const tauri = window.__TAURI__; 34 | 35 | if (!tauri) { 36 | console.log("DECORUM: Tauri API not found. Exiting."); 37 | console.log( 38 | "DECORUM: Set withGlobalTauri: true in tauri.conf.json to enable.", 39 | ); 40 | return; 41 | } 42 | 43 | const win = tauri.window.getCurrentWindow(); 44 | 45 | console.log("DECORUM: Waiting for [data-tauri-decorum-tb] ..."); 46 | 47 | waitForElm("[data-tauri-decorum-tb]").then((tbEl) => { 48 | const actions = document.createElement("div"); 49 | actions.className = "decorum-tb-actions"; 50 | actions.style.width = "fit-content"; 51 | actions.style.display = "flex"; 52 | actions.style.paddingRight = "0.5em"; 53 | actions.style.gap = "0.8125em"; 54 | 55 | // Create button func 56 | const createButton = (id) => { 57 | console.debug("createButton", id); 58 | const btn = document.createElement("button"); 59 | btn.id = "decorum-tb-" + id; 60 | btn.classList.add("decorum-tb-btn"); 61 | 62 | switch (id) { 63 | case "minimize": 64 | btn.innerHTML = windowMinimizeSvg; 65 | 66 | btn.addEventListener("click", () => { 67 | clearTimeout(timer); 68 | win.minimize(); 69 | }); 70 | 71 | break; 72 | case "maximize": 73 | btn.innerHTML = windowMaximizeSvg; 74 | win.onResized(() => { 75 | win.isMaximized().then((maximized) => { 76 | if (maximized) { 77 | btn.innerHTML = windowRestoreSvg; 78 | } else { 79 | btn.innerHTML = windowMaximizeSvg; 80 | } 81 | }); 82 | }); 83 | 84 | btn.addEventListener("click", () => { 85 | btn.blur(); 86 | win.toggleMaximize(); 87 | }); 88 | 89 | break; 90 | case "close": 91 | btn.innerHTML = windowCloseSvg; 92 | btn.addEventListener("click", () => win.close()); 93 | break; 94 | } 95 | 96 | actions.appendChild(btn); 97 | }; 98 | 99 | // Before eval-ing, the line below is modified from the rust side 100 | // to only include the controls that are enabled on the window 101 | ["minimize", "maximize", "close"].forEach(createButton); 102 | 103 | tbEl.appendChild(actions); 104 | 105 | const style = document.createElement("style"); 106 | document.head.appendChild(style); 107 | 108 | style.innerHTML = ` 109 | .decorum-tb-btn { 110 | color: white; 111 | cursor: default; 112 | border: none; 113 | padding: 0px; 114 | width: 1.5em; 115 | height: 1.5em; 116 | outline: none; 117 | display: flex; 118 | box-shadow: none; 119 | align-items: center; 120 | justify-content: center; 121 | transition: background 0.1s; 122 | border-radius: 50%; 123 | background-color: var(--decorum-tb-actions-icon-bg, rgba(255, 255, 255, 0.2)); 124 | } 125 | 126 | .decorum-tb-btn:hover { 127 | background-color: var(--decorum-tb-actions-icon-active-bg, rgba(255, 255, 255, 0.4)); 128 | } 129 | 130 | .decorum-tb-btn svg { 131 | width: 16px; 132 | height: 16px; 133 | } 134 | 135 | .decorum-tb-btn svg path { 136 | fill: var(--decorum-tb-actions-icon-fg, #ffffff); 137 | } 138 | `; 139 | }); 140 | }); 141 | -------------------------------------------------------------------------------- /src/js/controls.js: -------------------------------------------------------------------------------- 1 | function waitForElm(selector) { 2 | return new Promise((resolve) => { 3 | if (document.querySelector(selector)) { 4 | return resolve(document.querySelector(selector)); 5 | } 6 | 7 | const observer = new MutationObserver((mutations) => { 8 | if (document.querySelector(selector)) { 9 | observer.disconnect(); 10 | resolve(document.querySelector(selector)); 11 | } 12 | }); 13 | 14 | // If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336 15 | observer.observe(document.body, { 16 | childList: true, 17 | subtree: true, 18 | }); 19 | }); 20 | } 21 | 22 | document.addEventListener("DOMContentLoaded", () => { 23 | const tauri = window.__TAURI__; 24 | 25 | if (!tauri) { 26 | console.log("DECORUM: Tauri API not found. Exiting."); 27 | console.log( 28 | "DECORUM: Set withGlobalTauri: true in tauri.conf.json to enable.", 29 | ); 30 | return; 31 | } 32 | 33 | const win = tauri.window.getCurrentWindow(); 34 | const invoke = tauri.core.invoke; 35 | 36 | console.log("DECORUM: Waiting for [data-tauri-decorum-tb] ..."); 37 | 38 | // Add debounce function 39 | const debounce = (func, delay) => { 40 | let timeoutId; 41 | return (...args) => { 42 | clearTimeout(timeoutId); 43 | timeoutId = setTimeout(() => func(...args), delay); 44 | }; 45 | }; 46 | 47 | // Debounce the control creation 48 | const debouncedCreateControls = debounce(() => { 49 | const tbEl = document.querySelector("[data-tauri-decorum-tb]"); 50 | if (!tbEl) return; 51 | 52 | // Check if controls already exist 53 | if (tbEl.querySelector(".decorum-tb-btn")) { 54 | console.log("DECORUM: Controls already exist. Skipping creation."); 55 | return; 56 | } 57 | 58 | // Create button func 59 | const createButton = (id) => { 60 | const btn = document.createElement("button"); 61 | 62 | btn.id = "decorum-tb-" + id; 63 | btn.style.width = "58px"; 64 | btn.style.height = "32px"; 65 | btn.style.border = "none"; 66 | btn.style.padding = "0px"; 67 | btn.style.outline = "none"; 68 | btn.style.display = "flex"; 69 | btn.style.fontSize = "10px"; 70 | btn.style.fontWeight = "300"; 71 | btn.style.cursor = "default"; 72 | btn.style.boxShadow = "none"; 73 | btn.style.borderRadius = "0px"; 74 | btn.style.alignItems = "center"; 75 | btn.style.justifyContent = "center"; 76 | btn.style.transition = "background 0.1s"; 77 | btn.style.backgroundColor = "transparent"; 78 | btn.style.textRendering = "optimizeLegibility"; 79 | btn.style.fontFamily = "'Segoe Fluent Icons', 'Segoe MDL2 Assets'"; 80 | 81 | let timer; 82 | const show_snap_overlay = () => { 83 | win.setFocus().then(() => 84 | invoke("plugin:decorum|show_snap_overlay") 85 | ); 86 | }; 87 | 88 | // Setup hover events 89 | btn.addEventListener("mouseenter", () => { 90 | if (id === "close") { 91 | btn.style.backgroundColor = "rgba(255,0,0,0.7)"; 92 | } else { 93 | btn.style.backgroundColor = "rgba(0,0,0,0.2)"; 94 | } 95 | }); 96 | 97 | btn.addEventListener("mouseleave", () => { 98 | btn.style.backgroundColor = "transparent"; 99 | }); 100 | switch (id) { 101 | case "minimize": 102 | btn.innerHTML = "\uE921"; 103 | btn.setAttribute("aria-label", "Minimize window"); 104 | 105 | btn.addEventListener("click", () => { 106 | clearTimeout(timer); 107 | win.minimize(); 108 | }); 109 | 110 | break; 111 | case "maximize": 112 | btn.innerHTML = "\uE922"; 113 | btn.setAttribute("aria-label", "Maximize window"); 114 | win.onResized(() => { 115 | win.isMaximized().then((maximized) => { 116 | if (maximized) { 117 | btn.innerHTML = "\uE923"; 118 | btn.setAttribute( 119 | "aria-label", 120 | "Restore window size" 121 | ); 122 | } else { 123 | btn.innerHTML = "\uE922"; 124 | btn.setAttribute( 125 | "aria-label", 126 | "Maximize window size" 127 | ); 128 | } 129 | }); 130 | }); 131 | 132 | btn.addEventListener("click", () => { 133 | clearTimeout(timer); 134 | win.toggleMaximize(); 135 | }); 136 | btn.addEventListener("mouseleave", () => 137 | clearTimeout(timer) 138 | ); 139 | btn.addEventListener("mouseenter", () => { 140 | timer = setTimeout(show_snap_overlay, 620); 141 | }); 142 | break; 143 | case "close": 144 | btn.innerHTML = "\uE8BB"; 145 | btn.setAttribute("aria-label", "Close window"); 146 | btn.addEventListener("click", () => win.close()); 147 | break; 148 | } 149 | 150 | tbEl.appendChild(btn); 151 | }; 152 | 153 | // Before eval-ing, the line below is modified from the rust side 154 | // to only include the controls that are enabled on the window 155 | ["minimize", "maximize", "close"].forEach(createButton); 156 | }); 157 | 158 | // Use MutationObserver to watch for changes 159 | const observer = new MutationObserver((mutations) => { 160 | for (let mutation of mutations) { 161 | if (mutation.type === "childList") { 162 | const tbEl = document.querySelector("[data-tauri-decorum-tb]"); 163 | if (tbEl) { 164 | debouncedCreateControls(); 165 | break; 166 | } 167 | } 168 | } 169 | }); 170 | 171 | // data-tauri-decorum-tb may be created before observer starts 172 | if (document.querySelector("[data-tauri-decorum-tb]")) { 173 | debouncedCreateControls(); 174 | return; 175 | } 176 | 177 | observer.observe(document.body, { 178 | childList: true, 179 | subtree: true, 180 | }); 181 | 182 | debouncedCreateControls(); 183 | }); 184 | -------------------------------------------------------------------------------- /permissions/schemas/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "PermissionFile", 4 | "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", 5 | "type": "object", 6 | "properties": { 7 | "default": { 8 | "description": "The default permission set for the plugin", 9 | "anyOf": [ 10 | { 11 | "$ref": "#/definitions/DefaultPermission" 12 | }, 13 | { 14 | "type": "null" 15 | } 16 | ] 17 | }, 18 | "set": { 19 | "description": "A list of permissions sets defined", 20 | "type": "array", 21 | "items": { 22 | "$ref": "#/definitions/PermissionSet" 23 | } 24 | }, 25 | "permission": { 26 | "description": "A list of inlined permissions", 27 | "default": [], 28 | "type": "array", 29 | "items": { 30 | "$ref": "#/definitions/Permission" 31 | } 32 | } 33 | }, 34 | "definitions": { 35 | "DefaultPermission": { 36 | "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", 37 | "type": "object", 38 | "required": [ 39 | "permissions" 40 | ], 41 | "properties": { 42 | "version": { 43 | "description": "The version of the permission.", 44 | "type": [ 45 | "integer", 46 | "null" 47 | ], 48 | "format": "uint64", 49 | "minimum": 1.0 50 | }, 51 | "description": { 52 | "description": "Human-readable description of what the permission does. Tauri convention is to use

headings in markdown content for Tauri documentation generation purposes.", 53 | "type": [ 54 | "string", 55 | "null" 56 | ] 57 | }, 58 | "permissions": { 59 | "description": "All permissions this set contains.", 60 | "type": "array", 61 | "items": { 62 | "type": "string" 63 | } 64 | } 65 | } 66 | }, 67 | "PermissionSet": { 68 | "description": "A set of direct permissions grouped together under a new name.", 69 | "type": "object", 70 | "required": [ 71 | "description", 72 | "identifier", 73 | "permissions" 74 | ], 75 | "properties": { 76 | "identifier": { 77 | "description": "A unique identifier for the permission.", 78 | "type": "string" 79 | }, 80 | "description": { 81 | "description": "Human-readable description of what the permission does.", 82 | "type": "string" 83 | }, 84 | "permissions": { 85 | "description": "All permissions this set contains.", 86 | "type": "array", 87 | "items": { 88 | "$ref": "#/definitions/PermissionKind" 89 | } 90 | } 91 | } 92 | }, 93 | "Permission": { 94 | "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", 95 | "type": "object", 96 | "required": [ 97 | "identifier" 98 | ], 99 | "properties": { 100 | "version": { 101 | "description": "The version of the permission.", 102 | "type": [ 103 | "integer", 104 | "null" 105 | ], 106 | "format": "uint64", 107 | "minimum": 1.0 108 | }, 109 | "identifier": { 110 | "description": "A unique identifier for the permission.", 111 | "type": "string" 112 | }, 113 | "description": { 114 | "description": "Human-readable description of what the permission does. Tauri internal convention is to use

headings in markdown content for Tauri documentation generation purposes.", 115 | "type": [ 116 | "string", 117 | "null" 118 | ] 119 | }, 120 | "commands": { 121 | "description": "Allowed or denied commands when using this permission.", 122 | "default": { 123 | "allow": [], 124 | "deny": [] 125 | }, 126 | "allOf": [ 127 | { 128 | "$ref": "#/definitions/Commands" 129 | } 130 | ] 131 | }, 132 | "scope": { 133 | "description": "Allowed or denied scoped when using this permission.", 134 | "allOf": [ 135 | { 136 | "$ref": "#/definitions/Scopes" 137 | } 138 | ] 139 | }, 140 | "platforms": { 141 | "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", 142 | "type": [ 143 | "array", 144 | "null" 145 | ], 146 | "items": { 147 | "$ref": "#/definitions/Target" 148 | } 149 | } 150 | } 151 | }, 152 | "Commands": { 153 | "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", 154 | "type": "object", 155 | "properties": { 156 | "allow": { 157 | "description": "Allowed command.", 158 | "default": [], 159 | "type": "array", 160 | "items": { 161 | "type": "string" 162 | } 163 | }, 164 | "deny": { 165 | "description": "Denied command, which takes priority.", 166 | "default": [], 167 | "type": "array", 168 | "items": { 169 | "type": "string" 170 | } 171 | } 172 | } 173 | }, 174 | "Scopes": { 175 | "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", 176 | "type": "object", 177 | "properties": { 178 | "allow": { 179 | "description": "Data that defines what is allowed by the scope.", 180 | "type": [ 181 | "array", 182 | "null" 183 | ], 184 | "items": { 185 | "$ref": "#/definitions/Value" 186 | } 187 | }, 188 | "deny": { 189 | "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", 190 | "type": [ 191 | "array", 192 | "null" 193 | ], 194 | "items": { 195 | "$ref": "#/definitions/Value" 196 | } 197 | } 198 | } 199 | }, 200 | "Value": { 201 | "description": "All supported ACL values.", 202 | "anyOf": [ 203 | { 204 | "description": "Represents a null JSON value.", 205 | "type": "null" 206 | }, 207 | { 208 | "description": "Represents a [`bool`].", 209 | "type": "boolean" 210 | }, 211 | { 212 | "description": "Represents a valid ACL [`Number`].", 213 | "allOf": [ 214 | { 215 | "$ref": "#/definitions/Number" 216 | } 217 | ] 218 | }, 219 | { 220 | "description": "Represents a [`String`].", 221 | "type": "string" 222 | }, 223 | { 224 | "description": "Represents a list of other [`Value`]s.", 225 | "type": "array", 226 | "items": { 227 | "$ref": "#/definitions/Value" 228 | } 229 | }, 230 | { 231 | "description": "Represents a map of [`String`] keys to [`Value`]s.", 232 | "type": "object", 233 | "additionalProperties": { 234 | "$ref": "#/definitions/Value" 235 | } 236 | } 237 | ] 238 | }, 239 | "Number": { 240 | "description": "A valid ACL number.", 241 | "anyOf": [ 242 | { 243 | "description": "Represents an [`i64`].", 244 | "type": "integer", 245 | "format": "int64" 246 | }, 247 | { 248 | "description": "Represents a [`f64`].", 249 | "type": "number", 250 | "format": "double" 251 | } 252 | ] 253 | }, 254 | "Target": { 255 | "description": "Platform target.", 256 | "oneOf": [ 257 | { 258 | "description": "MacOS.", 259 | "type": "string", 260 | "enum": [ 261 | "macOS" 262 | ] 263 | }, 264 | { 265 | "description": "Windows.", 266 | "type": "string", 267 | "enum": [ 268 | "windows" 269 | ] 270 | }, 271 | { 272 | "description": "Linux.", 273 | "type": "string", 274 | "enum": [ 275 | "linux" 276 | ] 277 | }, 278 | { 279 | "description": "Android.", 280 | "type": "string", 281 | "enum": [ 282 | "android" 283 | ] 284 | }, 285 | { 286 | "description": "iOS.", 287 | "type": "string", 288 | "enum": [ 289 | "iOS" 290 | ] 291 | } 292 | ] 293 | }, 294 | "PermissionKind": { 295 | "type": "string", 296 | "oneOf": [ 297 | { 298 | "description": "Enables the show_snap_overlay command without any pre-configured scope.", 299 | "type": "string", 300 | "const": "allow-show-snap-overlay" 301 | }, 302 | { 303 | "description": "Denies the show_snap_overlay command without any pre-configured scope.", 304 | "type": "string", 305 | "const": "deny-show-snap-overlay" 306 | } 307 | ] 308 | } 309 | } 310 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use tauri::plugin::{Builder, TauriPlugin}; 2 | use tauri::{Emitter, Error, Listener, Runtime, WebviewWindow}; 3 | 4 | #[cfg(target_os = "macos")] 5 | mod traffic; 6 | 7 | #[cfg(target_os = "linux")] 8 | mod dconf; 9 | 10 | mod commands; 11 | 12 | #[cfg(target_os = "macos")] 13 | #[macro_use] 14 | extern crate objc; 15 | 16 | /// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the decorum APIs. 17 | pub trait WebviewWindowExt { 18 | fn create_overlay_titlebar(&self) -> Result<&WebviewWindow, Error>; 19 | #[cfg(target_os = "macos")] 20 | fn set_traffic_lights_inset(&self, x: f32, y: f32) -> Result<&WebviewWindow, Error>; 21 | #[cfg(target_os = "macos")] 22 | fn make_transparent(&self) -> Result<&WebviewWindow, Error>; 23 | #[cfg(target_os = "macos")] 24 | fn set_window_level(&self, level: u32) -> Result<&WebviewWindow, Error>; 25 | } 26 | 27 | impl<'a> WebviewWindowExt for WebviewWindow { 28 | /// Create a custom titlebar overlay. 29 | /// This will remove the default titlebar and create a draggable area for the titlebar. 30 | /// On Windows, it will also create custom window controls. 31 | fn create_overlay_titlebar(&self) -> Result<&WebviewWindow, Error> { 32 | #[cfg(target_os = "windows")] 33 | self.set_decorations(false)?; 34 | 35 | let win2 = self.clone(); 36 | 37 | self.listen("decorum-page-load", move |_event| { 38 | // println!("decorum-page-load event received") 39 | 40 | // Create a transparent draggable area for the titlebar 41 | let script_tb = include_str!("js/titlebar.js"); 42 | 43 | win2.eval(script_tb) 44 | .unwrap_or_else(|e| println!("decorum error: {:?}", e)); 45 | 46 | // Custom window controls for linux 47 | #[cfg(target_os = "linux")] 48 | { 49 | use linicon::{lookup_icon, IconType}; 50 | use std::io::prelude::*; 51 | let mut control_script = include_str!("./js/linux-controls.js").to_string(); 52 | 53 | let mut controls = Vec::new(); 54 | if win2.is_minimizable().unwrap_or(false) { 55 | controls.push("minimize".to_string()); 56 | } 57 | 58 | if win2.is_maximizable().unwrap_or(false) && win2.is_resizable().unwrap_or(false) { 59 | controls.push("maximize".to_string()); 60 | } 61 | 62 | if win2.is_closable().unwrap_or(false) { 63 | controls.push("close".to_string()); 64 | } 65 | 66 | controls.push("restore".to_string()); 67 | 68 | for control in controls.iter() { 69 | if let Some(Ok(control_icon)) = 70 | lookup_icon(format!("window-{}-symbolic", control)) 71 | .into_iter() 72 | .find(|icon| match icon { 73 | Ok(icon) => icon.icon_type == IconType::SVG, 74 | Err(_) => false, 75 | }) 76 | { 77 | let mut icon_data = String::new(); 78 | let mut f = std::fs::File::open(control_icon.path).unwrap(); 79 | let _ = f.read_to_string(&mut icon_data); 80 | 81 | control_script = 82 | control_script.replace(&format!("@win-{}", control), &icon_data); 83 | }; 84 | } 85 | 86 | controls.remove(controls.len() - 1); 87 | 88 | // return this string style 'appmenu:minimize,maximize,close' 89 | if let Ok(app_menu_config) = 90 | dconf::read("/org/gnome/desktop/wm/preferences/button-layout") 91 | { 92 | controls = app_menu_config 93 | .trim_start_matches("appmenu:") 94 | .split(',') 95 | .map(|x| x.to_string()) 96 | .collect::>(); 97 | }; 98 | 99 | let controls = format!("{:?}", controls); 100 | 101 | let control_script = control_script.replacen( 102 | "[\"minimize\", \"maximize\", \"close\"]", 103 | &controls, 104 | 1, 105 | ); 106 | 107 | win2.eval(&control_script).expect("couldn't run js"); 108 | } 109 | 110 | // On Windows, create custom window controls 111 | #[cfg(target_os = "windows")] 112 | { 113 | let mut controls = Vec::new(); 114 | 115 | if win2.is_minimizable().unwrap_or(false) { 116 | controls.push("minimize"); 117 | } 118 | 119 | if win2.is_maximizable().unwrap_or(false) && win2.is_resizable().unwrap_or(false) { 120 | controls.push("maximize"); 121 | } 122 | 123 | if win2.is_closable().unwrap_or(false) { 124 | controls.push("close"); 125 | } 126 | 127 | let script_controls = include_str!("js/controls.js"); 128 | let controls = format!("{:?}", controls); 129 | 130 | // this line finds ["minimize", "maximize", "close"] in the file 131 | // and replaces it with only the controls enabled for the window 132 | let script_controls = script_controls.replacen( 133 | "[\"minimize\", \"maximize\", \"close\"]", 134 | &controls, 135 | 1, 136 | ); 137 | 138 | win2.eval(script_controls.as_str()) 139 | .expect("couldn't run js"); 140 | 141 | let win3 = win2.clone(); 142 | win2.on_window_event(move |eve| match eve { 143 | tauri::WindowEvent::CloseRequested { .. } => { 144 | win3.unlisten(_event.id()); 145 | } 146 | _ => {} 147 | }); 148 | } 149 | }); 150 | 151 | Ok(self) 152 | } 153 | 154 | /// Set the inset of the traffic lights. 155 | /// This will move the traffic lights to the specified position. 156 | /// This is only available on macOS. 157 | #[cfg(target_os = "macos")] 158 | fn set_traffic_lights_inset(&self, x: f32, y: f32) -> Result<&WebviewWindow, Error> { 159 | ensure_main_thread(self, move |win| { 160 | let ns_window = win.ns_window()?; 161 | let ns_window_handle = traffic::UnsafeWindowHandle(ns_window); 162 | 163 | // Store the custom position in the window state 164 | traffic::update_traffic_light_positions(win, x.into(), y.into()); 165 | 166 | // Apply the position immediately 167 | traffic::position_traffic_lights(ns_window_handle, x.into(), y.into()); 168 | 169 | Ok(win) 170 | }) 171 | } 172 | 173 | /// Set the window background to transparent. 174 | /// This helper function is different from Tauri's default 175 | /// as it doesn't use the `transparent` flag or macOS Private APIs. 176 | #[cfg(target_os = "macos")] 177 | fn make_transparent(&self) -> Result<&WebviewWindow, Error> { 178 | use cocoa::{ 179 | appkit::NSColor, 180 | base::{id, nil}, 181 | foundation::NSString, 182 | }; 183 | 184 | // Make webview background transparent 185 | self.with_webview(|webview| unsafe { 186 | let id = webview.inner() as *mut objc::runtime::Object; 187 | let no: id = msg_send![class!(NSNumber), numberWithBool:0]; 188 | let _: id = 189 | msg_send![id, setValue:no forKey: NSString::alloc(nil).init_str("drawsBackground")]; 190 | })?; 191 | 192 | // Make window background transparent 193 | ensure_main_thread(self, move |win| { 194 | let ns_win = win.ns_window()? as id; 195 | unsafe { 196 | let win_bg_color = 197 | NSColor::colorWithSRGBRed_green_blue_alpha_(nil, 0.0, 0.0, 0.0, 0.0); 198 | let _: id = msg_send![ns_win, setBackgroundColor: win_bg_color]; 199 | } 200 | Ok(win) 201 | }) 202 | } 203 | 204 | /// Set the window level. 205 | /// This will set the window level to the specified value. 206 | /// NSWindowLevel values can be found [here](https://developer.apple.com/documentation/appkit/nswindowlevel?language=objc). 207 | /// This is only available on macOS. 208 | #[cfg(target_os = "macos")] 209 | fn set_window_level(&self, level: u32) -> Result<&WebviewWindow, Error> { 210 | ensure_main_thread(self, move |win| { 211 | let ns_win = win.ns_window()? as cocoa::base::id; 212 | unsafe { 213 | let _: () = msg_send![ns_win, setLevel: level]; 214 | } 215 | Ok(win) 216 | }) 217 | } 218 | } 219 | 220 | pub fn init() -> TauriPlugin { 221 | Builder::new("decorum") 222 | .invoke_handler(tauri::generate_handler![commands::show_snap_overlay]) 223 | .on_page_load(|win, _payload: &tauri::webview::PageLoadPayload| { 224 | match win.emit("decorum-page-load", ()) { 225 | Ok(_) => {} 226 | Err(e) => println!("decorum error: {:?}", e), 227 | } 228 | }) 229 | .on_window_ready(|_win| { 230 | #[cfg(target_os = "macos")] 231 | traffic::setup_traffic_light_positioner(_win); 232 | return; 233 | }) 234 | .build() 235 | } 236 | 237 | #[cfg(target_os = "macos")] 238 | fn is_main_thread() -> bool { 239 | std::thread::current().name() == Some("main") 240 | } 241 | 242 | #[cfg(target_os = "macos")] 243 | fn ensure_main_thread( 244 | win: &WebviewWindow, 245 | main_action: F, 246 | ) -> Result<&WebviewWindow, tauri::Error> 247 | where 248 | F: FnOnce(&WebviewWindow) -> Result<&WebviewWindow, Error> + Send + 'static, 249 | { 250 | match is_main_thread() { 251 | true => { 252 | main_action(win)?; 253 | Ok(win) 254 | } 255 | false => { 256 | let win2 = win.clone(); 257 | 258 | match win.run_on_main_thread(move || { 259 | main_action(&win2).unwrap(); 260 | }) { 261 | Ok(_) => Ok(win), 262 | Err(e) => Err(e), 263 | } 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@rollup/plugin-typescript@^11.1.6": 6 | version "11.1.6" 7 | resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-11.1.6.tgz#724237d5ec12609ec01429f619d2a3e7d4d1b22b" 8 | integrity sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA== 9 | dependencies: 10 | "@rollup/pluginutils" "^5.1.0" 11 | resolve "^1.22.1" 12 | 13 | "@rollup/pluginutils@^5.1.0": 14 | version "5.1.0" 15 | resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.0.tgz#7e53eddc8c7f483a4ad0b94afb1f7f5fd3c771e0" 16 | integrity sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g== 17 | dependencies: 18 | "@types/estree" "^1.0.0" 19 | estree-walker "^2.0.2" 20 | picomatch "^2.3.1" 21 | 22 | "@rollup/rollup-android-arm-eabi@4.17.2": 23 | version "4.17.2" 24 | resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz#1a32112822660ee104c5dd3a7c595e26100d4c2d" 25 | integrity sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ== 26 | 27 | "@rollup/rollup-android-arm64@4.17.2": 28 | version "4.17.2" 29 | resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz#5aeef206d65ff4db423f3a93f71af91b28662c5b" 30 | integrity sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw== 31 | 32 | "@rollup/rollup-darwin-arm64@4.17.2": 33 | version "4.17.2" 34 | resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz#6b66aaf003c70454c292cd5f0236ebdc6ffbdf1a" 35 | integrity sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw== 36 | 37 | "@rollup/rollup-darwin-x64@4.17.2": 38 | version "4.17.2" 39 | resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz#f64fc51ed12b19f883131ccbcea59fc68cbd6c0b" 40 | integrity sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ== 41 | 42 | "@rollup/rollup-linux-arm-gnueabihf@4.17.2": 43 | version "4.17.2" 44 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz#1a7641111be67c10111f7122d1e375d1226cbf14" 45 | integrity sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A== 46 | 47 | "@rollup/rollup-linux-arm-musleabihf@4.17.2": 48 | version "4.17.2" 49 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz#c93fd632923e0fee25aacd2ae414288d0b7455bb" 50 | integrity sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg== 51 | 52 | "@rollup/rollup-linux-arm64-gnu@4.17.2": 53 | version "4.17.2" 54 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz#fa531425dd21d058a630947527b4612d9d0b4a4a" 55 | integrity sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A== 56 | 57 | "@rollup/rollup-linux-arm64-musl@4.17.2": 58 | version "4.17.2" 59 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz#8acc16f095ceea5854caf7b07e73f7d1802ac5af" 60 | integrity sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA== 61 | 62 | "@rollup/rollup-linux-powerpc64le-gnu@4.17.2": 63 | version "4.17.2" 64 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz#94e69a8499b5cf368911b83a44bb230782aeb571" 65 | integrity sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ== 66 | 67 | "@rollup/rollup-linux-riscv64-gnu@4.17.2": 68 | version "4.17.2" 69 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz#7ef1c781c7e59e85a6ce261cc95d7f1e0b56db0f" 70 | integrity sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg== 71 | 72 | "@rollup/rollup-linux-s390x-gnu@4.17.2": 73 | version "4.17.2" 74 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz#f15775841c3232fca9b78cd25a7a0512c694b354" 75 | integrity sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g== 76 | 77 | "@rollup/rollup-linux-x64-gnu@4.17.2": 78 | version "4.17.2" 79 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz#b521d271798d037ad70c9f85dd97d25f8a52e811" 80 | integrity sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ== 81 | 82 | "@rollup/rollup-linux-x64-musl@4.17.2": 83 | version "4.17.2" 84 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz#9254019cc4baac35800991315d133cc9fd1bf385" 85 | integrity sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q== 86 | 87 | "@rollup/rollup-win32-arm64-msvc@4.17.2": 88 | version "4.17.2" 89 | resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz#27f65a89f6f52ee9426ec11e3571038e4671790f" 90 | integrity sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA== 91 | 92 | "@rollup/rollup-win32-ia32-msvc@4.17.2": 93 | version "4.17.2" 94 | resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz#a2fbf8246ed0bb014f078ca34ae6b377a90cb411" 95 | integrity sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ== 96 | 97 | "@rollup/rollup-win32-x64-msvc@4.17.2": 98 | version "4.17.2" 99 | resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz#5a2d08b81e8064b34242d5cc9973ef8dd1e60503" 100 | integrity sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w== 101 | 102 | "@tauri-apps/api@>=2.0.0-beta.24": 103 | version "2.0.0-rc.3" 104 | resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-rc.3.tgz#1dd17530de9cafd854f77d3feeca1732a985a81e" 105 | integrity sha512-k1erUfnoOFJwL5VNFZz0BQZ2agNstG7CNOjwpdWMl1vOaVuSn4DhJtXB0Deh9lZaaDlfrykKOyZs9c3XXpMi5Q== 106 | 107 | "@types/estree@1.0.5", "@types/estree@^1.0.0": 108 | version "1.0.5" 109 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" 110 | integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== 111 | 112 | estree-walker@^2.0.2: 113 | version "2.0.2" 114 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" 115 | integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== 116 | 117 | fsevents@~2.3.2: 118 | version "2.3.3" 119 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" 120 | integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== 121 | 122 | function-bind@^1.1.2: 123 | version "1.1.2" 124 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" 125 | integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== 126 | 127 | hasown@^2.0.0: 128 | version "2.0.2" 129 | resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" 130 | integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== 131 | dependencies: 132 | function-bind "^1.1.2" 133 | 134 | is-core-module@^2.13.0: 135 | version "2.13.1" 136 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" 137 | integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== 138 | dependencies: 139 | hasown "^2.0.0" 140 | 141 | path-parse@^1.0.7: 142 | version "1.0.7" 143 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 144 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 145 | 146 | picomatch@^2.3.1: 147 | version "2.3.1" 148 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 149 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 150 | 151 | resolve@^1.22.1: 152 | version "1.22.8" 153 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" 154 | integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== 155 | dependencies: 156 | is-core-module "^2.13.0" 157 | path-parse "^1.0.7" 158 | supports-preserve-symlinks-flag "^1.0.0" 159 | 160 | rollup@^4.9.6: 161 | version "4.17.2" 162 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.17.2.tgz#26d1785d0144122277fdb20ab3a24729ae68301f" 163 | integrity sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ== 164 | dependencies: 165 | "@types/estree" "1.0.5" 166 | optionalDependencies: 167 | "@rollup/rollup-android-arm-eabi" "4.17.2" 168 | "@rollup/rollup-android-arm64" "4.17.2" 169 | "@rollup/rollup-darwin-arm64" "4.17.2" 170 | "@rollup/rollup-darwin-x64" "4.17.2" 171 | "@rollup/rollup-linux-arm-gnueabihf" "4.17.2" 172 | "@rollup/rollup-linux-arm-musleabihf" "4.17.2" 173 | "@rollup/rollup-linux-arm64-gnu" "4.17.2" 174 | "@rollup/rollup-linux-arm64-musl" "4.17.2" 175 | "@rollup/rollup-linux-powerpc64le-gnu" "4.17.2" 176 | "@rollup/rollup-linux-riscv64-gnu" "4.17.2" 177 | "@rollup/rollup-linux-s390x-gnu" "4.17.2" 178 | "@rollup/rollup-linux-x64-gnu" "4.17.2" 179 | "@rollup/rollup-linux-x64-musl" "4.17.2" 180 | "@rollup/rollup-win32-arm64-msvc" "4.17.2" 181 | "@rollup/rollup-win32-ia32-msvc" "4.17.2" 182 | "@rollup/rollup-win32-x64-msvc" "4.17.2" 183 | fsevents "~2.3.2" 184 | 185 | supports-preserve-symlinks-flag@^1.0.0: 186 | version "1.0.0" 187 | resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" 188 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== 189 | 190 | tslib@^2.6.2: 191 | version "2.6.2" 192 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" 193 | integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== 194 | 195 | typescript@^5.3.3: 196 | version "5.4.5" 197 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" 198 | integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== 199 | -------------------------------------------------------------------------------- /src/traffic.rs: -------------------------------------------------------------------------------- 1 | // Most contents of this file are taken from Hoppscotch's tauri app. 2 | // I think there is work to be done to improve it, but I'm happy with it for now. 3 | // Reference source code is linked below. 4 | // https://github.com/hoppscotch/hoppscotch/blob/286fcd2bb08a84f027b10308d1e18da368f95ebf/packages/hoppscotch-selfhost-desktop/src-tauri/src/mac/window.rs 5 | 6 | use objc::{msg_send, sel, sel_impl}; 7 | use rand::{distributions::Alphanumeric, Rng}; 8 | use tauri::{Emitter, Runtime, Window}; 9 | 10 | const WINDOW_CONTROL_PAD_X: f64 = 12.0; 11 | const WINDOW_CONTROL_PAD_Y: f64 = 16.0; 12 | 13 | pub struct UnsafeWindowHandle(pub *mut std::ffi::c_void); 14 | unsafe impl Send for UnsafeWindowHandle {} 15 | unsafe impl Sync for UnsafeWindowHandle {} 16 | 17 | #[cfg(target_os = "macos")] 18 | pub fn position_traffic_lights(ns_window_handle: UnsafeWindowHandle, x: f64, y: f64) { 19 | use cocoa::appkit::{NSView, NSWindow, NSWindowButton}; 20 | use cocoa::foundation::NSRect; 21 | let ns_window = ns_window_handle.0 as cocoa::base::id; 22 | unsafe { 23 | let close = ns_window.standardWindowButton_(NSWindowButton::NSWindowCloseButton); 24 | let miniaturize = 25 | ns_window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton); 26 | let zoom = ns_window.standardWindowButton_(NSWindowButton::NSWindowZoomButton); 27 | 28 | // Check if close button exists and has a valid superview 29 | if close.is_null() { 30 | return; 31 | } 32 | 33 | let close_superview = close.superview(); 34 | if close_superview.is_null() { 35 | return; 36 | } 37 | 38 | let title_bar_container_view = close_superview.superview(); 39 | if title_bar_container_view.is_null() { 40 | return; 41 | } 42 | 43 | let close_rect: NSRect = msg_send![close, frame]; 44 | let button_height = close_rect.size.height; 45 | 46 | let title_bar_frame_height = button_height + y; 47 | let mut title_bar_rect = NSView::frame(title_bar_container_view); 48 | title_bar_rect.size.height = title_bar_frame_height; 49 | title_bar_rect.origin.y = NSView::frame(ns_window).size.height - title_bar_frame_height; 50 | let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect]; 51 | 52 | // Check if other buttons exist before using them 53 | let mut window_buttons = Vec::new(); 54 | if !close.is_null() { 55 | window_buttons.push(close); 56 | } 57 | if !miniaturize.is_null() { 58 | window_buttons.push(miniaturize); 59 | } 60 | if !zoom.is_null() { 61 | window_buttons.push(zoom); 62 | } 63 | 64 | if window_buttons.is_empty() { 65 | return; 66 | } 67 | 68 | let space_between = 20.0; // Fixed space between buttons 69 | let vertical_offset = 4.0; // Adjust this value to push buttons down 70 | 71 | for (i, button) in window_buttons.into_iter().enumerate() { 72 | let mut rect: NSRect = NSView::frame(button); 73 | rect.origin.x = x + (i as f64 * space_between); 74 | // Adjust vertical positioning 75 | rect.origin.y = ((title_bar_frame_height - button_height) / 2.0) - vertical_offset; 76 | button.setFrameOrigin(rect.origin); 77 | } 78 | } 79 | } 80 | 81 | #[cfg(target_os = "macos")] 82 | #[derive(Debug)] 83 | struct WindowState { 84 | window: Window, 85 | traffic_light_x: f64, 86 | traffic_light_y: f64, 87 | } 88 | 89 | #[cfg(target_os = "macos")] 90 | pub fn setup_traffic_light_positioner(window: Window) { 91 | use cocoa::appkit::{NSWindow, NSWindowButton}; 92 | use cocoa::base::{id, BOOL}; 93 | use cocoa::foundation::NSUInteger; 94 | use objc::runtime::{Object, Sel}; 95 | use std::ffi::c_void; 96 | 97 | // Check if this window has traffic lights before setting up positioning 98 | unsafe { 99 | let ns_win = match window.ns_window() { 100 | Ok(win) => win as id, 101 | Err(_) => return, 102 | }; 103 | 104 | // Quick check: if close button doesn't exist, this window probably doesn't have decorations 105 | let close = ns_win.standardWindowButton_(NSWindowButton::NSWindowCloseButton); 106 | if close.is_null() { 107 | return; 108 | } 109 | } 110 | 111 | // Do the initial positioning 112 | position_traffic_lights( 113 | UnsafeWindowHandle(window.ns_window().expect("Failed to create window handle")), 114 | WINDOW_CONTROL_PAD_X, 115 | WINDOW_CONTROL_PAD_Y, 116 | ); 117 | 118 | // Ensure they stay in place while resizing the window. 119 | fn with_window_state) -> T, T>( 120 | this: &Object, 121 | func: F, 122 | ) { 123 | let ptr = unsafe { 124 | let x: *mut c_void = *this.get_ivar("app_box"); 125 | &mut *(x as *mut WindowState) 126 | }; 127 | func(ptr); 128 | } 129 | 130 | unsafe { 131 | let ns_win = window 132 | .ns_window() 133 | .expect("NS Window should exist to mount traffic light delegate.") 134 | as id; 135 | 136 | let current_delegate: id = ns_win.delegate(); 137 | 138 | extern "C" fn on_window_should_close(this: &Object, _cmd: Sel, sender: id) -> BOOL { 139 | unsafe { 140 | let super_del: id = *this.get_ivar("super_delegate"); 141 | msg_send![super_del, windowShouldClose: sender] 142 | } 143 | } 144 | extern "C" fn on_window_will_close(this: &Object, _cmd: Sel, notification: id) { 145 | unsafe { 146 | let super_del: id = *this.get_ivar("super_delegate"); 147 | let _: () = msg_send![super_del, windowWillClose: notification]; 148 | } 149 | } 150 | extern "C" fn on_window_did_resize(this: &Object, _cmd: Sel, notification: id) { 151 | unsafe { 152 | with_window_state(&*this, |state: &mut WindowState| { 153 | let id = state 154 | .window 155 | .ns_window() 156 | .expect("NS window should exist on state to handle resize") 157 | as id; 158 | 159 | #[cfg(target_os = "macos")] 160 | position_traffic_lights( 161 | UnsafeWindowHandle(id as *mut std::ffi::c_void), 162 | state.traffic_light_x, 163 | state.traffic_light_y, 164 | ); 165 | }); 166 | 167 | let super_del: id = *this.get_ivar("super_delegate"); 168 | let _: () = msg_send![super_del, windowDidResize: notification]; 169 | } 170 | } 171 | extern "C" fn on_window_did_move(this: &Object, _cmd: Sel, notification: id) { 172 | unsafe { 173 | let super_del: id = *this.get_ivar("super_delegate"); 174 | let _: () = msg_send![super_del, windowDidMove: notification]; 175 | } 176 | } 177 | extern "C" fn on_window_did_change_backing_properties( 178 | this: &Object, 179 | _cmd: Sel, 180 | notification: id, 181 | ) { 182 | unsafe { 183 | let super_del: id = *this.get_ivar("super_delegate"); 184 | let _: () = msg_send![super_del, windowDidChangeBackingProperties: notification]; 185 | } 186 | } 187 | extern "C" fn on_window_did_become_key(this: &Object, _cmd: Sel, notification: id) { 188 | unsafe { 189 | let super_del: id = *this.get_ivar("super_delegate"); 190 | let _: () = msg_send![super_del, windowDidBecomeKey: notification]; 191 | } 192 | } 193 | extern "C" fn on_window_did_resign_key(this: &Object, _cmd: Sel, notification: id) { 194 | unsafe { 195 | let super_del: id = *this.get_ivar("super_delegate"); 196 | let _: () = msg_send![super_del, windowDidResignKey: notification]; 197 | } 198 | } 199 | extern "C" fn on_dragging_entered(this: &Object, _cmd: Sel, notification: id) -> BOOL { 200 | unsafe { 201 | let super_del: id = *this.get_ivar("super_delegate"); 202 | msg_send![super_del, draggingEntered: notification] 203 | } 204 | } 205 | extern "C" fn on_prepare_for_drag_operation( 206 | this: &Object, 207 | _cmd: Sel, 208 | notification: id, 209 | ) -> BOOL { 210 | unsafe { 211 | let super_del: id = *this.get_ivar("super_delegate"); 212 | msg_send![super_del, prepareForDragOperation: notification] 213 | } 214 | } 215 | extern "C" fn on_perform_drag_operation(this: &Object, _cmd: Sel, sender: id) -> BOOL { 216 | unsafe { 217 | let super_del: id = *this.get_ivar("super_delegate"); 218 | msg_send![super_del, performDragOperation: sender] 219 | } 220 | } 221 | extern "C" fn on_conclude_drag_operation(this: &Object, _cmd: Sel, notification: id) { 222 | unsafe { 223 | let super_del: id = *this.get_ivar("super_delegate"); 224 | let _: () = msg_send![super_del, concludeDragOperation: notification]; 225 | } 226 | } 227 | extern "C" fn on_dragging_exited(this: &Object, _cmd: Sel, notification: id) { 228 | unsafe { 229 | let super_del: id = *this.get_ivar("super_delegate"); 230 | let _: () = msg_send![super_del, draggingExited: notification]; 231 | } 232 | } 233 | extern "C" fn on_window_will_use_full_screen_presentation_options( 234 | this: &Object, 235 | _cmd: Sel, 236 | window: id, 237 | proposed_options: NSUInteger, 238 | ) -> NSUInteger { 239 | unsafe { 240 | let super_del: id = *this.get_ivar("super_delegate"); 241 | msg_send![super_del, window: window willUseFullScreenPresentationOptions: proposed_options] 242 | } 243 | } 244 | extern "C" fn on_window_did_enter_full_screen( 245 | this: &Object, 246 | _cmd: Sel, 247 | notification: id, 248 | ) { 249 | unsafe { 250 | with_window_state(&*this, |state: &mut WindowState| { 251 | state 252 | .window 253 | .emit("did-enter-fullscreen", ()) 254 | .expect("Failed to emit event"); 255 | }); 256 | 257 | let super_del: id = *this.get_ivar("super_delegate"); 258 | let _: () = msg_send![super_del, windowDidEnterFullScreen: notification]; 259 | } 260 | } 261 | extern "C" fn on_window_will_enter_full_screen( 262 | this: &Object, 263 | _cmd: Sel, 264 | notification: id, 265 | ) { 266 | unsafe { 267 | with_window_state(&*this, |state: &mut WindowState| { 268 | state 269 | .window 270 | .emit("will-enter-fullscreen", ()) 271 | .expect("Failed to emit event"); 272 | }); 273 | 274 | let super_del: id = *this.get_ivar("super_delegate"); 275 | let _: () = msg_send![super_del, windowWillEnterFullScreen: notification]; 276 | } 277 | } 278 | extern "C" fn on_window_did_exit_full_screen( 279 | this: &Object, 280 | _cmd: Sel, 281 | notification: id, 282 | ) { 283 | unsafe { 284 | with_window_state(&*this, |state: &mut WindowState| { 285 | state 286 | .window 287 | .emit("did-exit-fullscreen", ()) 288 | .expect("Failed to emit event"); 289 | 290 | let id = state.window.ns_window().expect("Failed to emit event") as id; 291 | position_traffic_lights( 292 | UnsafeWindowHandle(id as *mut std::ffi::c_void), 293 | state.traffic_light_x, 294 | state.traffic_light_y, 295 | ); 296 | }); 297 | 298 | let super_del: id = *this.get_ivar("super_delegate"); 299 | let _: () = msg_send![super_del, windowDidExitFullScreen: notification]; 300 | } 301 | } 302 | extern "C" fn on_window_will_exit_full_screen( 303 | this: &Object, 304 | _cmd: Sel, 305 | notification: id, 306 | ) { 307 | unsafe { 308 | with_window_state(&*this, |state: &mut WindowState| { 309 | state 310 | .window 311 | .emit("will-exit-fullscreen", ()) 312 | .expect("Failed to emit event"); 313 | }); 314 | 315 | let super_del: id = *this.get_ivar("super_delegate"); 316 | let _: () = msg_send![super_del, windowWillExitFullScreen: notification]; 317 | } 318 | } 319 | extern "C" fn on_window_did_fail_to_enter_full_screen( 320 | this: &Object, 321 | _cmd: Sel, 322 | window: id, 323 | ) { 324 | unsafe { 325 | let super_del: id = *this.get_ivar("super_delegate"); 326 | let _: () = msg_send![super_del, windowDidFailToEnterFullScreen: window]; 327 | } 328 | } 329 | extern "C" fn on_effective_appearance_did_change( 330 | this: &Object, 331 | _cmd: Sel, 332 | notification: id, 333 | ) { 334 | unsafe { 335 | let super_del: id = *this.get_ivar("super_delegate"); 336 | let _: () = msg_send![super_del, effectiveAppearanceDidChange: notification]; 337 | } 338 | } 339 | extern "C" fn on_effective_appearance_did_changed_on_main_thread( 340 | this: &Object, 341 | _cmd: Sel, 342 | notification: id, 343 | ) { 344 | unsafe { 345 | let super_del: id = *this.get_ivar("super_delegate"); 346 | let _: () = msg_send![ 347 | super_del, 348 | effectiveAppearanceDidChangedOnMainThread: notification 349 | ]; 350 | } 351 | } 352 | 353 | // Are we deallocing this properly ? (I miss safe Rust :( ) 354 | let window_label = window.label().to_string(); 355 | 356 | let app_state = WindowState { 357 | window, 358 | traffic_light_x: WINDOW_CONTROL_PAD_X, 359 | traffic_light_y: WINDOW_CONTROL_PAD_Y, 360 | }; 361 | let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void; 362 | let random_str: String = rand::thread_rng() 363 | .sample_iter(&Alphanumeric) 364 | .take(20) 365 | .map(char::from) 366 | .collect(); 367 | 368 | // We need to ensure we have a unique delegate name, otherwise we will panic while trying to create a duplicate 369 | // delegate with the same name. 370 | let delegate_name = format!("windowDelegate_{}_{}", window_label, random_str); 371 | 372 | ns_win.setDelegate_(cocoa::delegate!(&delegate_name, { 373 | window: id = ns_win, 374 | app_box: *mut c_void = app_box, 375 | toolbar: id = cocoa::base::nil, 376 | super_delegate: id = current_delegate, 377 | (windowShouldClose:) => on_window_should_close as extern fn(&Object, Sel, id) -> BOOL, 378 | (windowWillClose:) => on_window_will_close as extern fn(&Object, Sel, id), 379 | (windowDidResize:) => on_window_did_resize:: as extern fn(&Object, Sel, id), 380 | (windowDidMove:) => on_window_did_move as extern fn(&Object, Sel, id), 381 | (windowDidChangeBackingProperties:) => on_window_did_change_backing_properties as extern fn(&Object, Sel, id), 382 | (windowDidBecomeKey:) => on_window_did_become_key as extern fn(&Object, Sel, id), 383 | (windowDidResignKey:) => on_window_did_resign_key as extern fn(&Object, Sel, id), 384 | (draggingEntered:) => on_dragging_entered as extern fn(&Object, Sel, id) -> BOOL, 385 | (prepareForDragOperation:) => on_prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL, 386 | (performDragOperation:) => on_perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL, 387 | (concludeDragOperation:) => on_conclude_drag_operation as extern fn(&Object, Sel, id), 388 | (draggingExited:) => on_dragging_exited as extern fn(&Object, Sel, id), 389 | (window:willUseFullScreenPresentationOptions:) => on_window_will_use_full_screen_presentation_options as extern fn(&Object, Sel, id, NSUInteger) -> NSUInteger, 390 | (windowDidEnterFullScreen:) => on_window_did_enter_full_screen:: as extern fn(&Object, Sel, id), 391 | (windowWillEnterFullScreen:) => on_window_will_enter_full_screen:: as extern fn(&Object, Sel, id), 392 | (windowDidExitFullScreen:) => on_window_did_exit_full_screen:: as extern fn(&Object, Sel, id), 393 | (windowWillExitFullScreen:) => on_window_will_exit_full_screen:: as extern fn(&Object, Sel, id), 394 | (windowDidFailToEnterFullScreen:) => on_window_did_fail_to_enter_full_screen as extern fn(&Object, Sel, id), 395 | (effectiveAppearanceDidChange:) => on_effective_appearance_did_change as extern fn(&Object, Sel, id), 396 | (effectiveAppearanceDidChangedOnMainThread:) => on_effective_appearance_did_changed_on_main_thread as extern fn(&Object, Sel, id) 397 | })) 398 | } 399 | } 400 | 401 | #[cfg(target_os = "macos")] 402 | pub fn update_traffic_light_positions(window: &tauri::WebviewWindow, x: f64, y: f64) { 403 | use objc::runtime::Object; 404 | use std::ffi::c_void; 405 | use tauri::Wry; 406 | 407 | unsafe { 408 | let ns_win = match window.ns_window() { 409 | Ok(win) => win as cocoa::base::id, 410 | Err(_) => return, 411 | }; 412 | 413 | let delegate: *mut Object = msg_send![ns_win, delegate]; 414 | if delegate.is_null() { 415 | return; 416 | } 417 | 418 | // Try to access the ivar directly with proper type annotation 419 | let app_box: *mut c_void = match std::panic::catch_unwind(|| { 420 | *(*delegate).get_ivar::<*mut c_void>("app_box") 421 | }) { 422 | Ok(ptr) if !ptr.is_null() => ptr, 423 | _ => return, // Either the ivar doesn't exist or it's null 424 | }; 425 | 426 | // Specify Wry as the concrete runtime type 427 | let state: &mut WindowState = &mut *(app_box as *mut WindowState); 428 | state.traffic_light_x = x; 429 | state.traffic_light_y = y; 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /examples/tauri-app/src-tauri/gen/schemas/acl-manifests.json: -------------------------------------------------------------------------------- 1 | {"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["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-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null},"decorum":{"default_permission":null,"permissions":{"allow-show-snap-overlay":{"identifier":"allow-show-snap-overlay","description":"Enables the show_snap_overlay command without any pre-configured scope.","commands":{"allow":["show_snap_overlay"],"deny":[]}},"deny-show-snap-overlay":{"identifier":"deny-show-snap-overlay","description":"Denies the show_snap_overlay command without any pre-configured scope.","commands":{"allow":[],"deny":["show_snap_overlay"]}}},"permission_sets":{},"global_scope_schema":null}} --------------------------------------------------------------------------------