mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-05-03 12:15:11 +02:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f30a3b0501 | |||
| 7d332ef634 | |||
| e162e811fe | |||
| a8310f4149 | |||
| a171e0fcce | |||
| bf29a72baa | |||
| 70e2fadd91 | |||
| 3779fb5063 | |||
| 0b0088821e | |||
| f69a7805a7 | |||
| 4216c0517f | |||
| 909ff619e0 | |||
| 6ada2af2e1 | |||
| 8e1ae08c69 | |||
| 264a044097 | |||
| da8a7bbe3a | |||
| d9fa37ada5 | |||
| 98f8ef5a9f | |||
| e268c02ab6 | |||
| 1578970f81 | |||
| 04ce9c4f6c | |||
| a2eb3a6e43 | |||
| 60a8545cb7 | |||
| b344177d8b | |||
| 4b627f0dd5 | |||
| b76bf2d94b | |||
| 475ef7255f | |||
| 955fd90eec | |||
| 0d41e748cf | |||
| 706f32dd1d | |||
| 0f739dbc48 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"notification": patch
|
||||
---
|
||||
|
||||
Use notify_rust from crates.io instead of local fork.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"deep-link-js": patch
|
||||
---
|
||||
|
||||
Fixed a typo in the `deep-link` js bindings causing `isRegistered` to not work.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"http": "patch"
|
||||
---
|
||||
|
||||
Fix `ORIGIN` header containing 2 URLs when `unsafe-headers` feature flag is enabled.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"http": "patch"
|
||||
"http-js": "patch"
|
||||
---
|
||||
|
||||
Allow setting `Origin` header when `unsafe-headers` feature flag is active.
|
||||
@@ -9,6 +9,7 @@
|
||||
".changes/clipboard-mobile.md",
|
||||
".changes/clipboard-refactor.md",
|
||||
".changes/clipboard-text-command-rename.md",
|
||||
".changes/crate-notify-rust.md",
|
||||
".changes/deep-link-desktop.md",
|
||||
".changes/dialog-can-create-directories.md",
|
||||
".changes/dialog-linux-freeze.md",
|
||||
@@ -25,6 +26,7 @@
|
||||
".changes/fix-android-warnings.md",
|
||||
".changes/fix-authenticator-windows-compile.md",
|
||||
".changes/fix-autolaunch-macos.md",
|
||||
".changes/fix-deep-link-is-registered-typo.md",
|
||||
".changes/fix-deep-link-linux.md",
|
||||
".changes/fix-default-arg-value.md",
|
||||
".changes/fix-fs-scope-deadlock.md",
|
||||
@@ -42,6 +44,8 @@
|
||||
".changes/global-hotkey-event.md",
|
||||
".changes/global-shortcut-refactor.md",
|
||||
".changes/http-cookies.md",
|
||||
".changes/http-origin-duplicated.md",
|
||||
".changes/http-origin-unsafe.md",
|
||||
".changes/http-origin.md",
|
||||
".changes/http-tauri-beta-19.md",
|
||||
".changes/http-unsafe-headers.md",
|
||||
@@ -64,6 +68,7 @@
|
||||
".changes/shell-shellexcute.md",
|
||||
".changes/single-instance.macos.md",
|
||||
".changes/sql-column-order.md",
|
||||
".changes/sql-public-db-instances.md",
|
||||
".changes/target-sdk-34.md",
|
||||
".changes/tauri-beta-14-dependencies.md",
|
||||
".changes/tauri-beta-14.md",
|
||||
@@ -75,6 +80,7 @@
|
||||
".changes/tauri-beta-8.md",
|
||||
".changes/tauri-beta-9.md",
|
||||
".changes/updater-download-install-js-binding.md",
|
||||
".changes/updater-msiexec.md",
|
||||
".changes/updater-non-zip.md",
|
||||
".changes/updater-nsis-shortcuts.md",
|
||||
".changes/updater-zip-no-default-features.md",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"sql": patch
|
||||
---
|
||||
|
||||
Made `DbInstances` public for managing database instances directly from `Rust`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"updater": "patch"
|
||||
---
|
||||
|
||||
Fix regression in updater plugin failing to update using `.msi` installer.
|
||||
@@ -1,10 +0,0 @@
|
||||
target
|
||||
node_modules
|
||||
dist
|
||||
build/
|
||||
dist-js
|
||||
api-iife.js
|
||||
init-iife.js
|
||||
init.js
|
||||
rollup.config.js
|
||||
examples/
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"prettier",
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:security/recommended-legacy"
|
||||
],
|
||||
"overrides": [],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module",
|
||||
"project": ["**/tsconfig.json"]
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {}
|
||||
}
|
||||
@@ -18,3 +18,5 @@ jobs:
|
||||
id: covector
|
||||
with:
|
||||
command: "status"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
comment: true
|
||||
@@ -62,6 +62,7 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
command: "version-or-publish"
|
||||
createRelease: true
|
||||
recognizeContributors: true
|
||||
|
||||
- name: Create Pull Request With Versions Bumped
|
||||
id: cpr
|
||||
|
||||
@@ -55,6 +55,7 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
command: "version-or-publish"
|
||||
createRelease: true
|
||||
recognizeContributors: true
|
||||
|
||||
- name: Create Pull Request With Versions Bumped
|
||||
id: cpr
|
||||
|
||||
@@ -47,130 +47,105 @@ jobs:
|
||||
tauri-plugin-authenticator:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/authenticator/**
|
||||
tauri-plugin-autostart:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/autostart/**
|
||||
tauri-plugin-cli:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/cli/**
|
||||
tauri-plugin-clipboard-manager:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/clipboard-manager/**
|
||||
tauri-plugin-deep-link:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/deep-link/**
|
||||
tauri-plugin-dialog:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/dialog/**
|
||||
- plugins/fs/**
|
||||
tauri-plugin-fs:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/fs/**
|
||||
tauri-plugin-global-shortcut:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/global-shortcut/**
|
||||
tauri-plugin-http:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/http/**
|
||||
- plugins/fs/**
|
||||
tauri-plugin-localhost:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/localhost/**
|
||||
tauri-plugin-log:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/log/**
|
||||
tauri-plugin-notification:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/notification/**
|
||||
tauri-plugin-os:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/os/**
|
||||
tauri-plugin-persisted-scope:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/persisted-scope/**
|
||||
- plugins/fs/**
|
||||
tauri-plugin-positioner:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/positioner/**
|
||||
tauri-plugin-process:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/process/**
|
||||
tauri-plugin-shell:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/shell/**
|
||||
tauri-plugin-single-instance:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/single-instance/**
|
||||
tauri-plugin-sql:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/sql/**
|
||||
tauri-plugin-store:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/store/**
|
||||
tauri-plugin-stronghold:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/stronghold/**
|
||||
tauri-plugin-updater:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/updater/**
|
||||
tauri-plugin-upload:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/upload/**
|
||||
tauri-plugin-websocket:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/websocket/**
|
||||
tauri-plugin-window-state:
|
||||
- .github/workflows/test-rust.yml
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- plugins/window-state/**
|
||||
|
||||
test:
|
||||
|
||||
Generated
+529
-244
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@
|
||||
| [biometric](plugins/biometric) | Prompt the user for biometric authentication on Android and iOS. | ? | ? | ? | ✅ | ✅ |
|
||||
| [cli](plugins/cli) | Parse arguments from your Command Line Interface | ✅ | ✅ | ✅ | ? | ? |
|
||||
| [clipboard-manager](plugins/clipboard-manager) | Read and write to the system clipboard. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [deep-link](plugins/deep-link) | Set your Tauri application as the default handler for an URL. | ? | ? | ? | ✅ | ✅ |
|
||||
| [deep-link](plugins/deep-link) | Set your Tauri application as the default handler for an URL. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [dialog](plugins/dialog) | Native system dialogs for opening and saving files along with message dialogs. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [fs](plugins/fs) | Access the file system. | ✅ | ✅ | ✅ | ? | ? |
|
||||
| [global-shortcut](plugins/global-shortcut) | Register global shortcuts. | ✅ | ✅ | ✅ | ? | ? |
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import eslint from "@eslint/js";
|
||||
import eslintConfigPrettier from "eslint-config-prettier";
|
||||
import eslintPluginSecurity from "eslint-plugin-security";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
ignores: [
|
||||
"**/target",
|
||||
"**/node_modules",
|
||||
"**/examples",
|
||||
"**/dist",
|
||||
"**/dist-js",
|
||||
"**/build",
|
||||
"**/api-iife.js",
|
||||
"**/init-iife.js",
|
||||
"**/init.js",
|
||||
"**/rollup.config.js",
|
||||
".scripts",
|
||||
"eslint.config.js",
|
||||
],
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
eslintConfigPrettier,
|
||||
eslintPluginSecurity.configs.recommended,
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: { project: true, tsconfigRootDir: import.meta.dirname },
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.0.0-beta.9]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `http-js@2.0.0-beta.6`
|
||||
|
||||
## \[2.0.0-beta.8]
|
||||
|
||||
### Dependencies
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "svelte-app",
|
||||
"private": true,
|
||||
"version": "2.0.0-beta.8",
|
||||
"version": "2.0.0-beta.9",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --clearScreen false",
|
||||
@@ -17,7 +17,7 @@
|
||||
"@tauri-apps/plugin-dialog": "2.0.0-beta.5",
|
||||
"@tauri-apps/plugin-fs": "2.0.0-beta.5",
|
||||
"@tauri-apps/plugin-global-shortcut": "2.0.0-beta.5",
|
||||
"@tauri-apps/plugin-http": "2.0.0-beta.5",
|
||||
"@tauri-apps/plugin-http": "2.0.0-beta.6",
|
||||
"@tauri-apps/plugin-nfc": "2.0.0-beta.5",
|
||||
"@tauri-apps/plugin-notification": "2.0.0-beta.5",
|
||||
"@tauri-apps/plugin-os": "2.0.0-beta.5",
|
||||
@@ -31,10 +31,10 @@
|
||||
"@iconify-json/ph": "^1.1.8",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.1",
|
||||
"@tauri-apps/cli": "2.0.0-beta.20",
|
||||
"@unocss/extractor-svelte": "^0.60.0",
|
||||
"@unocss/extractor-svelte": "^0.61.0",
|
||||
"internal-ip": "7.0.0",
|
||||
"svelte": "^4.2.8",
|
||||
"unocss": "^0.60.0",
|
||||
"unocss": "^0.61.0",
|
||||
"vite": "^5.0.13"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.0.0-beta.11]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `notification@2.0.0-beta.8`
|
||||
- Upgraded to `http@2.0.0-beta.10`
|
||||
- Upgraded to `updater@2.0.0-beta.8`
|
||||
|
||||
## \[2.0.0-beta.10]
|
||||
|
||||
### Dependencies
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "api"
|
||||
publish = false
|
||||
version = "2.0.0-beta.10"
|
||||
version = "2.0.0-beta.11"
|
||||
description = "An example Tauri Application showcasing the api"
|
||||
edition = "2021"
|
||||
rust-version = { workspace = true }
|
||||
@@ -23,8 +23,8 @@ tauri-plugin-log = { path = "../../../plugins/log", version = "2.0.0-beta.6" }
|
||||
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.0.0-beta.9", features = [ "watch" ] }
|
||||
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.1.0-beta.4" }
|
||||
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.0.0-beta.9" }
|
||||
tauri-plugin-http = { path = "../../../plugins/http", features = [ "multipart" ], version = "2.0.0-beta.9" }
|
||||
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.0.0-beta.7", features = [ "windows7-compat" ] }
|
||||
tauri-plugin-http = { path = "../../../plugins/http", features = [ "multipart" ], version = "2.0.0-beta.10" }
|
||||
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.0.0-beta.8", features = [ "windows7-compat" ] }
|
||||
tauri-plugin-os = { path = "../../../plugins/os", version = "2.0.0-beta.6" }
|
||||
tauri-plugin-process = { path = "../../../plugins/process", version = "2.0.0-beta.6" }
|
||||
tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.0.0-beta.7" }
|
||||
@@ -43,7 +43,7 @@ tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.0.0-beta.7"
|
||||
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
||||
tauri-plugin-cli = { path = "../../../plugins/cli", version = "2.0.0-beta.6" }
|
||||
tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.0.0-beta.6" }
|
||||
tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.0.0-beta.7" }
|
||||
tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.0.0-beta.8" }
|
||||
|
||||
[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies]
|
||||
tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.0.0-beta.7" }
|
||||
|
||||
+9
-11
@@ -10,22 +10,20 @@
|
||||
"format-check": "prettier --check \"./**/*.{cjs,mjs,js,jsx,mts,ts,tsx,html,css,json}\" --ignore-path .prettierignore"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.5.0",
|
||||
"@rollup/plugin-node-resolve": "15.2.3",
|
||||
"@rollup/plugin-terser": "0.4.4",
|
||||
"@rollup/plugin-typescript": "11.1.6",
|
||||
"@typescript-eslint/eslint-plugin": "7.11.0",
|
||||
"@typescript-eslint/parser": "7.11.0",
|
||||
"covector": "^0.10.2",
|
||||
"eslint": "8.57.0",
|
||||
"@types/eslint__js": "8.42.3",
|
||||
"covector": "^0.11.0",
|
||||
"eslint": "9.5.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-n": "17.7.0",
|
||||
"eslint-plugin-promise": "6.2.0",
|
||||
"eslint-plugin-security": "3.0.0",
|
||||
"prettier": "3.2.5",
|
||||
"eslint-plugin-security": "3.0.1",
|
||||
"prettier": "3.3.2",
|
||||
"rollup": "4.18.0",
|
||||
"tslib": "2.6.2",
|
||||
"typescript": "5.4.5"
|
||||
"tslib": "2.6.3",
|
||||
"typescript": "5.4.5",
|
||||
"typescript-eslint": "rc-v8"
|
||||
},
|
||||
"resolutions": {
|
||||
"semver": ">=7.5.2",
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.0.0-beta.7]
|
||||
|
||||
- [`0b008882`](https://github.com/tauri-apps/plugins-workspace/commit/0b0088821e50e33825f7d573b1c826cfeb38dda0) ([#1404](https://github.com/tauri-apps/plugins-workspace/pull/1404) by [@simonhyll](https://github.com/tauri-apps/plugins-workspace/../../simonhyll)) Fixed a typo in the `deep-link` js bindings causing `isRegistered` to not work.
|
||||
|
||||
## \[2.0.0-beta.6]
|
||||
|
||||
- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22.
|
||||
|
||||
@@ -1 +1 @@
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_DEEPLINK__=function(e){"use strict";function n(e,n=!1){return window.__TAURI_INTERNALS__.transformCallback(e,n)}async function r(e,n={},r){return window.__TAURI_INTERNALS__.invoke(e,n,r)}var t;async function i(e,t,i){const a=(void 0,{kind:"Any"});return r("plugin:event|listen",{event:e,target:a,handler:n(t)}).then((n=>async()=>async function(e,n){await r("plugin:event|unlisten",{event:e,eventId:n})}(e,n)))}async function a(){return await r("plugin:deep-link|get_current")}return"function"==typeof SuppressedError&&SuppressedError,function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG="tauri://drag",e.DROP="tauri://drop",e.DROP_OVER="tauri://drop-over",e.DROP_CANCELLED="tauri://drag-cancelled"}(t||(t={})),e.getCurrent=a,e.isRegistered=async function(e){return await r("plugin:deep-link|i_registered",{protocol:e})},e.onOpenUrl=async function(e){const n=await a();return n&&e(n),await i("deep-link://new-url",(n=>{e(n.payload)}))},e.register=async function(e){return await r("plugin:deep-link|register",{protocol:e})},e.unregister=async function(e){return await r("plugin:deep-link|unregister",{protocol:e})},e}({});Object.defineProperty(window.__TAURI__,"deepLink",{value:__TAURI_PLUGIN_DEEPLINK__})}
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_DEEPLINK__=function(e){"use strict";function n(e,n=!1){return window.__TAURI_INTERNALS__.transformCallback(e,n)}async function r(e,n={},r){return window.__TAURI_INTERNALS__.invoke(e,n,r)}var t;async function i(e,t,i){const a=(void 0,{kind:"Any"});return r("plugin:event|listen",{event:e,target:a,handler:n(t)}).then((n=>async()=>async function(e,n){await r("plugin:event|unlisten",{event:e,eventId:n})}(e,n)))}async function a(){return await r("plugin:deep-link|get_current")}return"function"==typeof SuppressedError&&SuppressedError,function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG="tauri://drag",e.DROP="tauri://drop",e.DROP_OVER="tauri://drop-over",e.DROP_CANCELLED="tauri://drag-cancelled"}(t||(t={})),e.getCurrent=a,e.isRegistered=async function(e){return await r("plugin:deep-link|is_registered",{protocol:e})},e.onOpenUrl=async function(e){const n=await a();return n&&e(n),await i("deep-link://new-url",(n=>{e(n.payload)}))},e.register=async function(e){return await r("plugin:deep-link|register",{protocol:e})},e.unregister=async function(e){return await r("plugin:deep-link|unregister",{protocol:e})},e}({});Object.defineProperty(window.__TAURI__,"deepLink",{value:__TAURI_PLUGIN_DEEPLINK__})}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.0.0-beta.7]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `deep-link-js@2.0.0-beta.7`
|
||||
|
||||
## \[2.0.0-beta.6]
|
||||
|
||||
### Dependencies
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "deep-link-example",
|
||||
"private": true,
|
||||
"version": "2.0.0-beta.6",
|
||||
"version": "2.0.0-beta.7",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "2.0.0-beta.13",
|
||||
"@tauri-apps/plugin-deep-link": "2.0.0-beta.6"
|
||||
"@tauri-apps/plugin-deep-link": "2.0.0-beta.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "2.0.0-beta.20",
|
||||
|
||||
@@ -76,7 +76,7 @@ export async function unregister(protocol: string): Promise<null> {
|
||||
* @since 2.0.0
|
||||
*/
|
||||
export async function isRegistered(protocol: string): Promise<boolean> {
|
||||
return await invoke("plugin:deep-link|i_registered", { protocol });
|
||||
return await invoke("plugin:deep-link|is_registered", { protocol });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@tauri-apps/plugin-deep-link",
|
||||
"version": "2.0.0-beta.6",
|
||||
"version": "2.0.0-beta.7",
|
||||
"description": "Set your Tauri application as the default handler for an URL",
|
||||
"license": "MIT or APACHE-2.0",
|
||||
"authors": [
|
||||
|
||||
@@ -163,7 +163,6 @@ type OpenDialogReturn<T extends OpenDialogOptions> = T["directory"] extends true
|
||||
* @since 2.0.0
|
||||
*/
|
||||
async function open<T extends OpenDialogOptions>(
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
options: T = {} as T,
|
||||
): Promise<OpenDialogReturn<T>> {
|
||||
if (typeof options === "object") {
|
||||
|
||||
@@ -777,7 +777,6 @@ async function readTextFileLines(
|
||||
if (done) this.rid = null;
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
value: done ? "" : line!,
|
||||
done,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.0.0-beta.6]
|
||||
|
||||
- [`0f739dbc`](https://github.com/tauri-apps/plugins-workspace/commit/0f739dbc483a1f091977cbe575c3862fd39f8cf1) ([#1392](https://github.com/tauri-apps/plugins-workspace/pull/1392) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Allow setting `Origin` header when `unsafe-headers` feature flag is active.
|
||||
|
||||
## \[2.0.0-beta.5]
|
||||
|
||||
- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22.
|
||||
@@ -172,6 +176,23 @@
|
||||
lpha release!
|
||||
!
|
||||
371\)) First v2 alpha release!
|
||||
lpha release!
|
||||
lpha release!
|
||||
!
|
||||
371\)) First v2 alpha release!
|
||||
t v2 alpha release!
|
||||
!
|
||||
371\)) First v2 alpha release!
|
||||
ace/pull/371)) First v2 alpha release!
|
||||
371\)) First v2 alpha release!
|
||||
!
|
||||
371\)) First v2 alpha release!
|
||||
!
|
||||
371\)) First v2 alpha release!
|
||||
!
|
||||
371\)) First v2 alpha release!
|
||||
lpha release!
|
||||
!
|
||||
371\)) First v2 alpha release!
|
||||
lpha release!
|
||||
!
|
||||
371\)) First v2 alpha release!
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-http"
|
||||
version = "2.0.0-beta.9"
|
||||
version = "2.0.0-beta.10"
|
||||
description = "Access an HTTP client written in Rust."
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
|
||||
@@ -7,16 +7,18 @@
|
||||
*
|
||||
* ## Security
|
||||
*
|
||||
* This API has a scope configuration that forces you to restrict the URLs and paths that can be accessed using glob patterns.
|
||||
* This API has a scope configuration that forces you to restrict the URLs that can be accessed using glob patterns.
|
||||
*
|
||||
* For instance, this scope configuration only allows making HTTP requests to the GitHub API for the `tauri-apps` organization:
|
||||
* For instance, this scope configuration only allows making HTTP requests to all subdomains for `tauri.app` except for `https://private.tauri.app`:
|
||||
* ```json
|
||||
* {
|
||||
* "plugins": {
|
||||
* "http": {
|
||||
* "scope": ["https://api.github.com/repos/tauri-apps/*"]
|
||||
* "permissions": [
|
||||
* {
|
||||
* "identifier": "http:default",
|
||||
* "allow": [{ "url": "https://*.tauri.app" }],
|
||||
* "deny": [{ "url": "https://private.tauri.app" }]
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* ```
|
||||
* Trying to execute any API with a URL not configured on the scope results in a promise rejection due to denied access.
|
||||
@@ -141,6 +143,7 @@ export async function fetch(
|
||||
? headers
|
||||
: Object.entries(headers);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const mappedHeaders: Array<[string, string]> = headersArray.map(
|
||||
([name, val]) => [
|
||||
name,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@tauri-apps/plugin-http",
|
||||
"version": "2.0.0-beta.5",
|
||||
"version": "2.0.0-beta.6",
|
||||
"license": "MIT or APACHE-2.0",
|
||||
"authors": [
|
||||
"Tauri Programme within The Commons Conservancy"
|
||||
|
||||
@@ -201,29 +201,7 @@ pub async fn fetch<R: Runtime>(
|
||||
for (name, value) in &headers {
|
||||
let name = HeaderName::from_bytes(name.as_bytes())?;
|
||||
#[cfg(not(feature = "unsafe-headers"))]
|
||||
if matches!(
|
||||
name,
|
||||
// forbidden headers per fetch spec https://fetch.spec.whatwg.org/#terminology-headers
|
||||
header::ACCEPT_CHARSET
|
||||
| header::ACCEPT_ENCODING
|
||||
| header::ACCESS_CONTROL_REQUEST_HEADERS
|
||||
| header::ACCESS_CONTROL_REQUEST_METHOD
|
||||
| header::CONNECTION
|
||||
| header::CONTENT_LENGTH
|
||||
| header::COOKIE
|
||||
| header::DATE
|
||||
| header::DNT
|
||||
| header::EXPECT
|
||||
| header::HOST
|
||||
| header::ORIGIN
|
||||
| header::REFERER
|
||||
| header::SET_COOKIE
|
||||
| header::TE
|
||||
| header::TRAILER
|
||||
| header::TRANSFER_ENCODING
|
||||
| header::UPGRADE
|
||||
| header::VIA
|
||||
) {
|
||||
if is_unsafe_header(&name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -246,7 +224,14 @@ pub async fn fetch<R: Runtime>(
|
||||
request = request.header(header::USER_AGENT, "tauri-plugin-http");
|
||||
}
|
||||
|
||||
request = request.header(header::ORIGIN, webview.url()?.as_str());
|
||||
if cfg!(feature = "unsafe-headers")
|
||||
&& !headers.contains_key(header::ORIGIN.as_str())
|
||||
{
|
||||
if let Ok(url) = webview.url() {
|
||||
request =
|
||||
request.header(header::ORIGIN, url.origin().ascii_serialization());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(data) = data {
|
||||
request = request.body(data);
|
||||
@@ -343,3 +328,33 @@ pub(crate) async fn fetch_read_body<R: Runtime>(
|
||||
let res = Arc::into_inner(res).unwrap().0;
|
||||
Ok(tauri::ipc::Response::new(res.bytes().await?.to_vec()))
|
||||
}
|
||||
|
||||
// forbidden headers per fetch spec https://fetch.spec.whatwg.org/#terminology-headers
|
||||
#[cfg(not(feature = "unsafe-headers"))]
|
||||
fn is_unsafe_header(header: &HeaderName) -> bool {
|
||||
matches!(
|
||||
*header,
|
||||
header::ACCEPT_CHARSET
|
||||
| header::ACCEPT_ENCODING
|
||||
| header::ACCESS_CONTROL_REQUEST_HEADERS
|
||||
| header::ACCESS_CONTROL_REQUEST_METHOD
|
||||
| header::CONNECTION
|
||||
| header::CONTENT_LENGTH
|
||||
| header::COOKIE
|
||||
| header::DATE
|
||||
| header::DNT
|
||||
| header::EXPECT
|
||||
| header::HOST
|
||||
| header::ORIGIN
|
||||
| header::REFERER
|
||||
| header::SET_COOKIE
|
||||
| header::TE
|
||||
| header::TRAILER
|
||||
| header::TRANSFER_ENCODING
|
||||
| header::UPGRADE
|
||||
| header::VIA
|
||||
) || {
|
||||
let lower = header.as_str().to_lowercase();
|
||||
lower.starts_with("proxy-") || lower.starts_with("sec-")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ time = { version = "0.3", features = [ "formatting", "local-offset" ] }
|
||||
fern = "0.6"
|
||||
|
||||
[target."cfg(target_os = \"android\")".dependencies]
|
||||
android_logger = "0.13"
|
||||
android_logger = "0.14"
|
||||
|
||||
[target."cfg(target_os = \"ios\")".dependencies]
|
||||
swift-rs = "1"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.0.0-beta.8]
|
||||
|
||||
- [`3779fb50`](https://github.com/tauri-apps/plugins-workspace/commit/3779fb50634fba4d7e7eb0bfecc2216349b9d64d) ([#1432](https://github.com/tauri-apps/plugins-workspace/pull/1432) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Use notify_rust from crates.io instead of local fork.
|
||||
|
||||
## \[2.0.0-beta.5]
|
||||
|
||||
- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22.
|
||||
@@ -82,5 +86,5 @@
|
||||
workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!
|
||||
ithub.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!
|
||||
717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!
|
||||
!
|
||||
!
|
||||
717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-notification"
|
||||
version = "2.0.0-beta.7"
|
||||
version = "2.0.0-beta.8"
|
||||
description = "Send desktop and mobile notifications on your Tauri application."
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
@@ -32,18 +32,8 @@ serde_repr = "0.1"
|
||||
win7-notifications = { version = "0.4.3", optional = true }
|
||||
windows-version = { version = "0.1", optional = true }
|
||||
|
||||
[target."cfg(all(unix, not(target_os = \"macos\")))".dependencies]
|
||||
lazy_static = { version = "1", optional = true }
|
||||
zbus = { version = "4", optional = true }
|
||||
log = "0.4"
|
||||
env_logger = { version = "0.10", optional = true }
|
||||
|
||||
[target."cfg(target_os=\"macos\")".dependencies]
|
||||
mac-notification-sys = "0.6"
|
||||
chrono = { version = "0.4", optional = true }
|
||||
|
||||
[target."cfg(target_os=\"windows\")".dependencies]
|
||||
winrt-notification = { package = "tauri-winrt-notification", version = "0.3.1" }
|
||||
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
||||
notify-rust = "4.11"
|
||||
|
||||
[dev-dependencies]
|
||||
color-backtrace = "0.6"
|
||||
@@ -51,6 +41,4 @@ ctor = "0.2"
|
||||
maplit = "1"
|
||||
|
||||
[features]
|
||||
default = [ "zbus", "async" ]
|
||||
async = [ ]
|
||||
windows7-compat = [ "win7-notifications", "windows-version" ]
|
||||
|
||||
@@ -364,10 +364,8 @@ async function requestPermission(): Promise<Permission> {
|
||||
*/
|
||||
function sendNotification(options: Options | string): void {
|
||||
if (typeof options === "string") {
|
||||
// eslint-disable-next-line no-new
|
||||
new window.Notification(options);
|
||||
} else {
|
||||
// eslint-disable-next-line no-new
|
||||
new window.Notification(options.title, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,11 +57,12 @@ import type { Options } from "./index";
|
||||
|
||||
// @ts-expect-error unfortunately we can't implement the whole type, so we overwrite it with our own version
|
||||
window.Notification = function (title, options) {
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const opts = options || {};
|
||||
void sendNotification(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
Object.assign(opts, {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
title,
|
||||
}),
|
||||
);
|
||||
@@ -77,6 +78,7 @@ import type { Options } from "./index";
|
||||
if (!permissionSettable) {
|
||||
throw new Error("Readonly property");
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
permissionValue = v;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -160,7 +160,7 @@ mod imp {
|
||||
deprecated = "This function does not work on Windows 7. Use `Self::notify` instead."
|
||||
)]
|
||||
pub fn show(self) -> crate::Result<()> {
|
||||
let mut notification = crate::notify_rust::Notification::new();
|
||||
let mut notification = notify_rust::Notification::new();
|
||||
if let Some(body) = self.body {
|
||||
notification.body(&body);
|
||||
}
|
||||
@@ -186,7 +186,7 @@ mod imp {
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let _ = crate::notify_rust::set_application(if tauri::is_dev() {
|
||||
let _ = notify_rust::set_application(if tauri::is_dev() {
|
||||
"com.apple.Terminal"
|
||||
} else {
|
||||
&self.identifier
|
||||
|
||||
@@ -32,9 +32,6 @@ mod commands;
|
||||
mod error;
|
||||
mod models;
|
||||
|
||||
#[allow(dead_code, unused_imports, deprecated, clippy::derivable_impls)]
|
||||
mod notify_rust;
|
||||
|
||||
pub use error::{Error, Result};
|
||||
|
||||
#[cfg(desktop)]
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
|
||||
use super::image::ImageError;
|
||||
use std::{fmt, num};
|
||||
/// Convenient wrapper around `std::Result`.
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use super::macos::{ApplicationError, MacOsError, NotificationError};
|
||||
|
||||
/// The Error type.
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
kind: ErrorKind,
|
||||
}
|
||||
|
||||
/// The kind of an error.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum ErrorKind {
|
||||
/// only here for backwards compatibility
|
||||
Msg(String),
|
||||
|
||||
#[cfg(all(feature = "dbus", unix, not(target_os = "macos")))]
|
||||
Dbus(dbus::Error),
|
||||
|
||||
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
|
||||
Zbus(zbus::Error),
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
MacNotificationSys(mac_notification_sys::error::Error),
|
||||
|
||||
Parse(num::ParseIntError),
|
||||
|
||||
SpecVersion(String),
|
||||
|
||||
Conversion(String),
|
||||
|
||||
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
|
||||
Image(ImageError),
|
||||
|
||||
ImplementationMissing,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.kind {
|
||||
#[cfg(all(feature = "dbus", unix, not(target_os = "macos")))]
|
||||
ErrorKind::Dbus(ref e) => write!(f, "{}", e),
|
||||
|
||||
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
|
||||
ErrorKind::Zbus(ref e) => write!(f, "{}", e),
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
ErrorKind::MacNotificationSys(ref e) => write!(f, "{}", e),
|
||||
|
||||
ErrorKind::Parse(ref e) => write!(f, "Parsing Error: {}", e),
|
||||
ErrorKind::Conversion(ref e) => write!(f, "Conversion Error: {}", e),
|
||||
ErrorKind::SpecVersion(ref e) | ErrorKind::Msg(ref e) => write!(f, "{}", e),
|
||||
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
|
||||
ErrorKind::Image(ref e) => write!(f, "{}", e),
|
||||
ErrorKind::ImplementationMissing => write!(
|
||||
f,
|
||||
r#"No Dbus implementation available, please compile with either feature ="z" or feature="d""#
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl From<&str> for Error {
|
||||
fn from(e: &str) -> Error {
|
||||
Error {
|
||||
kind: ErrorKind::Msg(e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "dbus", unix, not(target_os = "macos")))]
|
||||
impl From<dbus::Error> for Error {
|
||||
fn from(e: dbus::Error) -> Error {
|
||||
Error {
|
||||
kind: ErrorKind::Dbus(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
|
||||
impl From<zbus::Error> for Error {
|
||||
fn from(e: zbus::Error) -> Error {
|
||||
Error {
|
||||
kind: ErrorKind::Zbus(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
impl From<mac_notification_sys::error::Error> for Error {
|
||||
fn from(e: mac_notification_sys::error::Error) -> Error {
|
||||
Error {
|
||||
kind: ErrorKind::MacNotificationSys(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
|
||||
impl From<ImageError> for Error {
|
||||
fn from(e: ImageError) -> Error {
|
||||
Error {
|
||||
kind: ErrorKind::Image(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<num::ParseIntError> for Error {
|
||||
fn from(e: num::ParseIntError) -> Error {
|
||||
Error {
|
||||
kind: ErrorKind::Parse(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ErrorKind> for Error {
|
||||
fn from(kind: ErrorKind) -> Error {
|
||||
Error { kind }
|
||||
}
|
||||
}
|
||||
|
||||
/// Just the usual bail macro
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! bail {
|
||||
($e:expr) => {
|
||||
return Err($e.into());
|
||||
};
|
||||
($fmt:expr, $($arg:tt)+) => {
|
||||
return Err(format!($fmt, $($arg)+).into());
|
||||
};
|
||||
}
|
||||
|
||||
/// Exits a function early with an `Error` if the condition is not satisfied.
|
||||
///
|
||||
/// Similar to `assert!`, `ensure!` takes a condition and exits the function
|
||||
/// if the condition fails. Unlike `assert!`, `ensure!` returns an `Error`,
|
||||
/// it does not panic.
|
||||
#[macro_export(local_inner_macros)]
|
||||
#[doc(hidden)]
|
||||
macro_rules! ensure {
|
||||
($cond:expr, $e:expr) => {
|
||||
if !($cond) {
|
||||
bail!($e);
|
||||
}
|
||||
};
|
||||
($cond:expr, $fmt:expr, $($arg:tt)*) => {
|
||||
if !($cond) {
|
||||
bail!($fmt, $($arg)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
|
||||
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
|
||||
use zbus::zvariant;
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub(crate) mod message;
|
||||
|
||||
#[cfg(all(feature = "images", any(feature = "dbus", feature = "zbus"), unix, not(target_os = "macos")))]
|
||||
use super::image::Image;
|
||||
|
||||
#[cfg(all(feature = "images", feature = "zbus", unix, not(target_os = "macos")))]
|
||||
use super::image::image_spec_str;
|
||||
use super::Urgency;
|
||||
|
||||
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))] use super::notification::Notification;
|
||||
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))] use std::collections::HashMap;
|
||||
|
||||
mod constants;
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
|
||||
pub(crate) enum CustomHintType {
|
||||
Int,
|
||||
String,
|
||||
}
|
||||
|
||||
/// `Hints` allow you to pass extra information to the server.
|
||||
///
|
||||
/// Many of these are standardized by either:
|
||||
///
|
||||
/// * <http://www.galago-project.org/specs/notification/0.9/x344.html>
|
||||
/// * <https://developer.gnome.org/notification-spec/#hints>
|
||||
///
|
||||
/// Which of these are actually implemented depends strongly on the Notification server you talk to.
|
||||
/// Usually the [`get_capabilities()`](`crate::get_capabilities`) gives some clues, but the standards usually mention much more
|
||||
/// than is actually available.
|
||||
///
|
||||
/// you pass these to [`Notification::hint`]
|
||||
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
|
||||
pub enum Hint {
|
||||
/// If true, server may interpret action identifiers as named icons and display those.
|
||||
ActionIcons(bool),
|
||||
|
||||
/// Check out:
|
||||
///
|
||||
/// * <http://www.galago-project.org/specs/notification/0.9/x211.html>
|
||||
/// * <https://developer.gnome.org/notification-spec/#categories>
|
||||
Category(String),
|
||||
|
||||
/// Name of the `DesktopEntry` representing the calling application. In case of "firefox.desktop"
|
||||
/// use "firefox". May be used to retrieve the correct icon.
|
||||
DesktopEntry(String),
|
||||
|
||||
/// Image as raw data
|
||||
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
|
||||
ImageData(Image),
|
||||
|
||||
/// Display the image at this path.
|
||||
ImagePath(String),
|
||||
|
||||
/// This does not work on all servers, however timeout=0 will do the job
|
||||
Resident(bool),
|
||||
|
||||
/// Play the sound at this path.
|
||||
SoundFile(String),
|
||||
|
||||
/// A themeable named sound from the freedesktop.org [sound naming specification](http://0pointer.de/public/sound-naming-spec.html) to play when the notification pops up. Similar to icon-name, only for sounds. An example would be "message-new-instant".
|
||||
SoundName(String),
|
||||
|
||||
/// Suppress the notification sound.
|
||||
SuppressSound(bool),
|
||||
|
||||
/// When set the server will treat the notification as transient and by-pass the server's persistence capability, if it should exist.
|
||||
Transient(bool),
|
||||
|
||||
/// Lets the notification point to a certain 'x' position on the screen.
|
||||
/// Requires `Y`.
|
||||
X(i32),
|
||||
|
||||
/// Lets the notification point to a certain 'y' position on the screen.
|
||||
/// Requires `X`.
|
||||
Y(i32),
|
||||
|
||||
/// Pass me a Urgency, either Low, Normal or Critical
|
||||
Urgency(Urgency),
|
||||
|
||||
/// If you want to pass something entirely different.
|
||||
Custom(String, String),
|
||||
|
||||
/// A custom numerical (integer) hint
|
||||
CustomInt(String, i32),
|
||||
|
||||
/// Only used by this `NotificationServer` implementation
|
||||
Invalid // TODO find a better solution to this
|
||||
}
|
||||
|
||||
impl Hint {
|
||||
/// Get the `bool` representation of this hint.
|
||||
pub fn as_bool(&self) -> Option<bool> {
|
||||
match *self {
|
||||
| Hint::ActionIcons(inner)
|
||||
| Hint::Resident(inner)
|
||||
| Hint::SuppressSound(inner)
|
||||
| Hint::Transient(inner) => Some(inner),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the `i32` representation of this hint.
|
||||
pub fn as_i32(&self) -> Option<i32> {
|
||||
match *self {
|
||||
Hint::X(inner) | Hint::Y(inner) => Some(inner),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the `&str` representation of this hint.
|
||||
pub fn as_str(&self) -> Option<&str> {
|
||||
match *self {
|
||||
Hint::DesktopEntry(ref inner) |
|
||||
Hint::ImagePath(ref inner) |
|
||||
Hint::SoundFile(ref inner) |
|
||||
Hint::SoundName(ref inner) => Some(inner),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
/// convenience converting a name and value into a hint
|
||||
pub fn from_key_val(name: &str, value: &str) -> Result<Hint, String> {
|
||||
match (name,value){
|
||||
(constants::ACTION_ICONS,val) => val.parse::<bool>().map(Hint::ActionIcons).map_err(|e|e.to_string()),
|
||||
(constants::CATEGORY, val) => Ok(Hint::Category(val.to_owned())),
|
||||
(constants::DESKTOP_ENTRY, val) => Ok(Hint::DesktopEntry(val.to_owned())),
|
||||
(constants::IMAGE_PATH, val) => Ok(Hint::ImagePath(val.to_owned())),
|
||||
(constants::RESIDENT, val) => val.parse::<bool>().map(Hint::Resident).map_err(|e|e.to_string()),
|
||||
(constants::SOUND_FILE, val) => Ok(Hint::SoundFile(val.to_owned())),
|
||||
(constants::SOUND_NAME, val) => Ok(Hint::SoundName(val.to_owned())),
|
||||
(constants::SUPPRESS_SOUND, val) => val.parse::<bool>().map(Hint::SuppressSound).map_err(|e|e.to_string()),
|
||||
(constants::TRANSIENT, val) => val.parse::<bool>().map(Hint::Transient).map_err(|e|e.to_string()),
|
||||
(constants::X, val) => val.parse::<i32>().map(Hint::X).map_err(|e|e.to_string()),
|
||||
(constants::Y, val) => val.parse::<i32>().map(Hint::Y).map_err(|e|e.to_string()),
|
||||
_ => Err(String::from("unknown name"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
impl Hint {}
|
||||
|
||||
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
|
||||
#[test]
|
||||
fn test_hints_to_map() {
|
||||
|
||||
// custom value should only be there once if the names are identical
|
||||
|
||||
let n1 = Notification::new()
|
||||
.hint(Hint::Custom("foo".into(), "bar1".into()))
|
||||
.hint(Hint::Custom("foo".into(), "bar2".into()))
|
||||
.hint(Hint::Custom("f00".into(), "bar3".into()))
|
||||
.finalize();
|
||||
|
||||
assert_eq!(hints_to_map(&n1), maplit::hashmap!{
|
||||
"foo" => zvariant::Value::Str("bar2".into()),
|
||||
"f00" => zvariant::Value::Str("bar3".into())
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
|
||||
pub(crate) fn hints_to_map(notification: &Notification) -> HashMap::<&str, zvariant::Value<'_>> {
|
||||
notification
|
||||
.get_hints()
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
|
||||
impl<'a> From<&'a Hint> for (&'a str, zvariant::Value<'a>) {
|
||||
fn from(val: &'a Hint) -> Self {
|
||||
use self::constants::*;
|
||||
match val {
|
||||
Hint::ActionIcons(value) => (ACTION_ICONS , zvariant::Value::Bool(*value)), // bool
|
||||
Hint::Category(value) => (CATEGORY , zvariant::Value::Str(value.as_str().into())),
|
||||
Hint::DesktopEntry(value) => (DESKTOP_ENTRY , zvariant::Value::Str(value.as_str().into())),
|
||||
|
||||
#[cfg(all(feature = "zbus", feature = "images", unix, not(target_os = "macos")))]
|
||||
//Hint::ImageData(image) => (image_spec(*crate::SPEC_VERSION).as_str(), ImagePayload::from(*image).into()),
|
||||
Hint::ImageData(image) => (
|
||||
image_spec_str(*crate::SPEC_VERSION),
|
||||
zvariant::Value::Structure(
|
||||
image.to_tuple().into()
|
||||
)
|
||||
),
|
||||
|
||||
|
||||
Hint::ImagePath(value) => (IMAGE_PATH , zvariant::Value::Str(value.as_str().into())),
|
||||
Hint::Resident(value) => (RESIDENT , zvariant::Value::Bool(*value)), // bool
|
||||
Hint::SoundFile(value) => (SOUND_FILE , zvariant::Value::Str(value.as_str().into())),
|
||||
Hint::SoundName(value) => (SOUND_NAME , zvariant::Value::Str(value.as_str().into())),
|
||||
Hint::SuppressSound(value) => (SUPPRESS_SOUND , zvariant::Value::Bool(*value)),
|
||||
Hint::Transient(value) => (TRANSIENT , zvariant::Value::Bool(*value)),
|
||||
Hint::X(value) => (X , zvariant::Value::I32(*value)),
|
||||
Hint::Y(value) => (Y , zvariant::Value::I32(*value)),
|
||||
Hint::Urgency(value) => (URGENCY , zvariant::Value::U8(*value as u8)),
|
||||
Hint::Custom(key, val) => (key.as_str() , zvariant::Value::Str(val.as_str().into())),
|
||||
Hint::CustomInt(key, val) => (key.as_str() , zvariant::Value::I32(*val)),
|
||||
Hint::Invalid => (INVALID , zvariant::Value::Str(INVALID.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(all(feature = "dbus", unix, not(target_os = "macos")))]
|
||||
impl<'a, A: dbus::arg::RefArg> From<(&'a String, &'a A)> for Hint {
|
||||
fn from(pair: (&String, &A)) -> Self {
|
||||
|
||||
let (key, variant) = pair;
|
||||
match (key.as_ref(), variant.as_u64(), variant.as_i64(), variant.as_str().map(String::from)) {
|
||||
|
||||
(constants::ACTION_ICONS, Some(1), _, _ ) => Hint::ActionIcons(true),
|
||||
(constants::ACTION_ICONS, _, _, _ ) => Hint::ActionIcons(false),
|
||||
(constants::URGENCY, level, _, _ ) => Hint::Urgency(level.into()),
|
||||
(constants::CATEGORY, _, _, Some(name) ) => Hint::Category(name),
|
||||
|
||||
(constants::DESKTOP_ENTRY, _, _, Some(entry)) => Hint::DesktopEntry(entry),
|
||||
(constants::IMAGE_PATH, _, _, Some(path) ) => Hint::ImagePath(path),
|
||||
(constants::RESIDENT, Some(1), _, _ ) => Hint::Resident(true),
|
||||
(constants::RESIDENT, _, _, _ ) => Hint::Resident(false),
|
||||
|
||||
(constants::SOUND_FILE, _, _, Some(path) ) => Hint::SoundFile(path),
|
||||
(constants::SOUND_NAME, _, _, Some(name) ) => Hint::SoundName(name),
|
||||
(constants::SUPPRESS_SOUND, Some(1), _, _ ) => Hint::SuppressSound(true),
|
||||
(constants::SUPPRESS_SOUND, _, _, _ ) => Hint::SuppressSound(false),
|
||||
(constants::TRANSIENT, Some(1), _, _ ) => Hint::Transient(true),
|
||||
(constants::TRANSIENT, _, _, _ ) => Hint::Transient(false),
|
||||
(constants::X, _, Some(x), _ ) => Hint::X(x as i32),
|
||||
(constants::Y, _, Some(y), _ ) => Hint::Y(y as i32),
|
||||
|
||||
other => {
|
||||
eprintln!("Invalid Hint {:#?} ", other);
|
||||
Hint::Invalid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub const ACTION_ICONS: &str = "action-icons";
|
||||
pub const CATEGORY: &str = "category";
|
||||
pub const DESKTOP_ENTRY: &str = "desktop-entry";
|
||||
pub const IMAGE_PATH: &str = "image-path";
|
||||
pub const RESIDENT: &str = "resident";
|
||||
pub const SOUND_FILE: &str = "sound-file";
|
||||
pub const SOUND_NAME: &str = "sound-name";
|
||||
pub const SUPPRESS_SOUND: &str = "suppress-sound";
|
||||
pub const TRANSIENT: &str = "transient";
|
||||
pub const X: &str = "x";
|
||||
pub const Y: &str = "y";
|
||||
pub const URGENCY: &str = "urgency";
|
||||
|
||||
pub const INVALID: &str = "invalid";
|
||||
@@ -1,159 +0,0 @@
|
||||
//! `Hints` allow you to pass extra information to the server.
|
||||
//!
|
||||
//! Many of these are standardized by either:
|
||||
//!
|
||||
//! [galago-project spec](http://www.galago-project.org/specs/notification/0.9/x344.html) or
|
||||
//! [gnome notification-spec](https://developer.gnome.org/notification-spec/#hints)
|
||||
//!
|
||||
//! Which of these are actually implemented depends strongly on the Notification server you talk to.
|
||||
//! Usually the `get_capabilities()` gives some clues, but the standards usually mention much more
|
||||
//! than is actually available.
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(dead_code, unused_imports)]
|
||||
|
||||
|
||||
use super::{Hint, constants::*};
|
||||
use super::Urgency;
|
||||
|
||||
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
|
||||
use super::image::*;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
#[cfg(feature = "dbus")]
|
||||
use dbus::arg::{messageitem::MessageItem, RefArg};
|
||||
|
||||
/// All currently implemented `Hints` that can be sent.
|
||||
///
|
||||
/// as found on <https://developer.gnome.org/notification-spec/>
|
||||
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
|
||||
pub(crate) struct HintMessage(Hint);
|
||||
|
||||
#[cfg(feature = "dbus")]
|
||||
impl HintMessage {
|
||||
pub fn wrap_hint(hint: Hint) -> (MessageItem, MessageItem) {
|
||||
Self::from(hint).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hint> for HintMessage {
|
||||
fn from(hint: Hint) -> Self {
|
||||
HintMessage(hint)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for HintMessage {
|
||||
type Target = Hint;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dbus")]
|
||||
impl<'a, A: RefArg> From<(&'a String, &'a A)> for HintMessage {
|
||||
fn from(pair: (&String, &A)) -> Self {
|
||||
|
||||
let (key, variant) = pair;
|
||||
match (key.as_ref(), variant.as_u64(), variant.as_i64(), variant.as_str().map(String::from)) {
|
||||
|
||||
(ACTION_ICONS, Some(1), _, _ ) => Hint::ActionIcons(true),
|
||||
(ACTION_ICONS, _, _, _ ) => Hint::ActionIcons(false),
|
||||
(URGENCY, level, _, _ ) => Hint::Urgency(level.into()),
|
||||
(CATEGORY, _, _, Some(name) ) => Hint::Category(name),
|
||||
|
||||
(DESKTOP_ENTRY, _, _, Some(entry)) => Hint::DesktopEntry(entry),
|
||||
(IMAGE_PATH, _, _, Some(path) ) => Hint::ImagePath(path),
|
||||
(RESIDENT, Some(1), _, _ ) => Hint::Resident(true),
|
||||
(RESIDENT, _, _, _ ) => Hint::Resident(false),
|
||||
|
||||
(SOUND_FILE, _, _, Some(path) ) => Hint::SoundFile(path),
|
||||
(SOUND_NAME, _, _, Some(name) ) => Hint::SoundName(name),
|
||||
(SUPPRESS_SOUND, Some(1), _, _ ) => Hint::SuppressSound(true),
|
||||
(SUPPRESS_SOUND, _, _, _ ) => Hint::SuppressSound(false),
|
||||
(TRANSIENT, Some(1), _, _ ) => Hint::Transient(true),
|
||||
(TRANSIENT, _, _, _ ) => Hint::Transient(false),
|
||||
(X, _, Some(x), _ ) => Hint::X(x as i32),
|
||||
(Y, _, Some(y), _ ) => Hint::Y(y as i32),
|
||||
|
||||
other => {
|
||||
eprintln!("Invalid Hint{:#?} ", other);
|
||||
Hint::Invalid
|
||||
}
|
||||
}.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dbus")]
|
||||
impl From<HintMessage> for (MessageItem, MessageItem) {
|
||||
fn from(hint: HintMessage) -> Self {
|
||||
|
||||
let (key, value): (String, MessageItem) = match hint.0 {
|
||||
Hint::ActionIcons(value) => (ACTION_ICONS .to_owned(), MessageItem::Bool(value)), // bool
|
||||
Hint::Category(ref value) => (CATEGORY .to_owned(), MessageItem::Str(value.clone())),
|
||||
Hint::DesktopEntry(ref value) => (DESKTOP_ENTRY .to_owned(), MessageItem::Str(value.clone())),
|
||||
#[cfg(all(feature = "images", unix, not(target_os ="macos")))]
|
||||
Hint::ImageData(image) => (image_spec(*crate::SPEC_VERSION), ImageMessage::from(image).into()),
|
||||
Hint::ImagePath(ref value) => (IMAGE_PATH .to_owned(), MessageItem::Str(value.clone())),
|
||||
Hint::Resident(value) => (RESIDENT .to_owned(), MessageItem::Bool(value)), // bool
|
||||
Hint::SoundFile(ref value) => (SOUND_FILE .to_owned(), MessageItem::Str(value.clone())),
|
||||
Hint::SoundName(ref value) => (SOUND_NAME .to_owned(), MessageItem::Str(value.clone())),
|
||||
Hint::SuppressSound(value) => (SUPPRESS_SOUND .to_owned(), MessageItem::Bool(value)),
|
||||
Hint::Transient(value) => (TRANSIENT .to_owned(), MessageItem::Bool(value)),
|
||||
Hint::X(value) => (X .to_owned(), MessageItem::Int32(value)),
|
||||
Hint::Y(value) => (Y .to_owned(), MessageItem::Int32(value)),
|
||||
Hint::Urgency(value) => (URGENCY .to_owned(), MessageItem::Byte(value as u8)),
|
||||
Hint::Custom(ref key, ref val) => (key .to_owned(), MessageItem::Str(val.to_owned ())),
|
||||
Hint::CustomInt(ref key, val) => (key .to_owned(), MessageItem::Int32(val)),
|
||||
Hint::Invalid => ("invalid" .to_owned(), MessageItem::Str("Invalid".to_owned()))
|
||||
};
|
||||
|
||||
(MessageItem::Str(key), MessageItem::Variant(Box::new(value)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: deprecated, Prefer the DBus Arg and RefArg APIs
|
||||
#[cfg(feature = "dbus")]
|
||||
impl From<(&MessageItem, &MessageItem)> for HintMessage {
|
||||
fn from ((key, mut value): (&MessageItem, &MessageItem)) -> Self {
|
||||
use Hint as Hint;
|
||||
|
||||
// If this is a variant, consider the thing inside it
|
||||
// If it's a nested variant, keep drilling down until we get a real value
|
||||
while let MessageItem::Variant(inner) = value {
|
||||
value = inner;
|
||||
}
|
||||
|
||||
let is_stringy = value.inner::<&str>().is_ok();
|
||||
|
||||
match key.inner::<&str>() {
|
||||
Ok(CATEGORY) => value.inner::<&str>().map(String::from).map(Hint::Category),
|
||||
Ok(ACTION_ICONS) => value.inner().map(Hint::ActionIcons),
|
||||
Ok(DESKTOP_ENTRY) => value.inner::<&str>().map(String::from).map(Hint::DesktopEntry),
|
||||
Ok(IMAGE_PATH) => value.inner::<&str>().map(String::from).map(Hint::ImagePath),
|
||||
Ok(RESIDENT) => value.inner().map(Hint::Resident),
|
||||
Ok(SOUND_FILE) => value.inner::<&str>().map(String::from).map(Hint::SoundFile),
|
||||
Ok(SOUND_NAME) => value.inner::<&str>().map(String::from).map(Hint::SoundName),
|
||||
Ok(SUPPRESS_SOUND) => value.inner().map(Hint::SuppressSound),
|
||||
Ok(TRANSIENT) => value.inner().map(Hint::Transient),
|
||||
Ok(X) => value.inner().map(Hint::X),
|
||||
Ok(Y) => value.inner().map(Hint::Y),
|
||||
Ok(URGENCY) => value.inner().map(|i| match i {
|
||||
0 => Urgency::Low,
|
||||
2 => Urgency::Critical,
|
||||
_ => Urgency::Normal
|
||||
}).map(Hint::Urgency),
|
||||
Ok(k) if is_stringy => value.inner::<&str>().map(|v| Hint::Custom(k.to_string(), v.to_string())),
|
||||
Ok(k) => value.inner().map(|v| Hint::CustomInt(k.to_string(), v)),
|
||||
_ => Err(()),
|
||||
}.unwrap_or(Hint::Invalid)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[cfg(feature = "dbus")]
|
||||
pub(crate) fn hints_from_variants<A: RefArg>(hints: &HashMap<String, A>) -> HashSet<HintMessage> {
|
||||
hints.iter().map(Into::into).collect()
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
#![cfg(all(test, unix, not(target_os = "macos")))]
|
||||
|
||||
use ctor::ctor;
|
||||
use dbus::arg::messageitem::MessageItem as Item;
|
||||
|
||||
use self::Hint;
|
||||
use super::Urgency::*;
|
||||
use super::*;
|
||||
|
||||
#[ctor]
|
||||
fn init_color_backtrace() {
|
||||
color_backtrace::install();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hint_to_item() {
|
||||
let category = &Hint::Category("test-me".to_owned());
|
||||
let (k, v) = category.into();
|
||||
|
||||
let test_k = Item::Str("category".into());
|
||||
let test_v = Item::Variant(Box::new(Item::Str("test-me".into())));
|
||||
|
||||
assert_eq!(k, test_k);
|
||||
assert_eq!(v, test_v);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn urgency() {
|
||||
let low = &Hint::Urgency(Low);
|
||||
let (k, v) = low.into();
|
||||
|
||||
let test_k = Item::Str("urgency".into());
|
||||
let test_v = Item::Variant(Box::new(Item::Byte(0)));
|
||||
|
||||
assert_eq!(k, test_k);
|
||||
assert_eq!(v, test_v);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_hint_to_item() {
|
||||
let old_hint = &Hint::Custom("foo".into(), "bar".into());
|
||||
|
||||
let (k, v) = old_hint.into();
|
||||
let hint: Hint = (&k, &v).into();
|
||||
|
||||
assert_eq!(old_hint, &hint);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
|
||||
fn imagedata_hint_to_item() {
|
||||
let hint = &Hint::ImageData(Image::from_rgb(1, 1, vec![0, 0, 0]).unwrap());
|
||||
let item: MessageItem = hint.into();
|
||||
let test_item = Item::DictEntry(
|
||||
Box::new(Item::Str(image_spec(*::SPEC_VERSION))),
|
||||
Box::new(Item::Variant(Box::new(Item::Struct(vec![
|
||||
Item::Int32(1),
|
||||
Item::Int32(1),
|
||||
Item::Int32(3),
|
||||
Item::Bool(false),
|
||||
Item::Int32(8),
|
||||
Item::Int32(3),
|
||||
Item::Array(
|
||||
dbus::MessageItemArray::new(
|
||||
vec![Item::Byte(0), Item::Byte(0), Item::Byte(0)],
|
||||
"ay".into(),
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
])))),
|
||||
);
|
||||
assert_eq!(item, test_item);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
|
||||
fn imagedata_hint_to_item_with_spec() {
|
||||
let key = image_spec(Version::new(1, 0));
|
||||
assert_eq!(key, String::from("icon_data"));
|
||||
|
||||
let key = image_spec(Version::new(1, 1));
|
||||
assert_eq!(key, String::from("image_data"));
|
||||
|
||||
let key = image_spec(Version::new(1, 2));
|
||||
assert_eq!(key, String::from("image-data"));
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
#[cfg(feature = "dbus")]
|
||||
use dbus::arg::messageitem::{MessageItem, MessageItemArray};
|
||||
pub use image::DynamicImage;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::convert::TryFrom;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
|
||||
use super::miniver::Version;
|
||||
|
||||
mod constants {
|
||||
pub const IMAGE_DATA: &str = "image-data";
|
||||
pub const IMAGE_DATA_1_1: &str = "image_data";
|
||||
pub const IMAGE_DATA_1_0: &str = "icon_data";
|
||||
}
|
||||
|
||||
/// Image representation for images. Send via `Notification::image_data()`
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Hash)]
|
||||
pub struct Image {
|
||||
width: i32,
|
||||
height: i32,
|
||||
rowstride: i32,
|
||||
alpha: bool,
|
||||
bits_per_sample: i32,
|
||||
channels: i32,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
fn from_raw_data(
|
||||
width: i32,
|
||||
height: i32,
|
||||
data: Vec<u8>,
|
||||
channels: i32,
|
||||
bits_per_sample: i32,
|
||||
alpha: bool,
|
||||
) -> Result<Self, ImageError> {
|
||||
const MAX_SIZE: i32 = 0x0fff_ffff;
|
||||
if width > MAX_SIZE || height > MAX_SIZE {
|
||||
return Err(ImageError::TooBig);
|
||||
}
|
||||
|
||||
if data.len() != (width * height * channels) as usize {
|
||||
Err(ImageError::WrongDataSize)
|
||||
} else {
|
||||
Ok(Self {
|
||||
width,
|
||||
height,
|
||||
bits_per_sample,
|
||||
channels,
|
||||
data,
|
||||
rowstride: width * channels,
|
||||
alpha,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an image from a raw vector of bytes
|
||||
pub fn from_rgb(width: i32, height: i32, data: Vec<u8>) -> Result<Self, ImageError> {
|
||||
let channels = 3i32;
|
||||
let bits_per_sample = 8;
|
||||
Self::from_raw_data(width, height, data, channels, bits_per_sample, false)
|
||||
}
|
||||
|
||||
/// Creates an image from a raw vector of bytes with alpha
|
||||
pub fn from_rgba(width: i32, height: i32, data: Vec<u8>) -> Result<Self, ImageError> {
|
||||
let channels = 4i32;
|
||||
let bits_per_sample = 8;
|
||||
Self::from_raw_data(width, height, data, channels, bits_per_sample, true)
|
||||
}
|
||||
|
||||
/// Attempts to open the given path as image
|
||||
pub fn open<T: AsRef<Path> + Sized>(path: T) -> Result<Self, ImageError> {
|
||||
let dyn_img = image::open(&path).map_err(ImageError::CantOpen)?;
|
||||
Image::try_from(dyn_img)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "images", feature = "zbus"))]
|
||||
pub(crate) fn to_tuple(&self) -> (i32, i32, i32, bool, i32, i32, Vec<u8>) {
|
||||
(
|
||||
self.width,
|
||||
self.height,
|
||||
self.rowstride,
|
||||
self.alpha,
|
||||
self.bits_per_sample,
|
||||
self.channels,
|
||||
self.data.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<DynamicImage> for Image {
|
||||
type Error = ImageError;
|
||||
|
||||
fn try_from(dyn_img: DynamicImage) -> Result<Self, Self::Error> {
|
||||
match dyn_img {
|
||||
DynamicImage::ImageRgb8(img) => Self::try_from(img),
|
||||
DynamicImage::ImageRgba8(img) => Self::try_from(img),
|
||||
_ => Err(ImageError::CantConvert),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<image::RgbImage> for Image {
|
||||
type Error = ImageError;
|
||||
|
||||
fn try_from(img: image::RgbImage) -> Result<Self, Self::Error> {
|
||||
let (width, height) = img.dimensions();
|
||||
let image_data = img.into_raw();
|
||||
Image::from_rgb(width as i32, height as i32, image_data)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<image::RgbaImage> for Image {
|
||||
type Error = ImageError;
|
||||
|
||||
fn try_from(img: image::RgbaImage) -> Result<Self, Self::Error> {
|
||||
let (width, height) = img.dimensions();
|
||||
let image_data = img.into_raw();
|
||||
Image::from_rgba(width as i32, height as i32, image_data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur when creating an Image
|
||||
#[derive(Debug)]
|
||||
pub enum ImageError {
|
||||
/// The given image is too big. DBus only has 32 bits for width / height
|
||||
TooBig,
|
||||
/// The given bytes don't match the width, height and channel count
|
||||
WrongDataSize,
|
||||
/// Can't open given path
|
||||
CantOpen(image::ImageError),
|
||||
/// Can't convert from given input
|
||||
CantConvert,
|
||||
}
|
||||
|
||||
impl Error for ImageError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
use ImageError::*;
|
||||
match self {
|
||||
TooBig | WrongDataSize | CantConvert => None,
|
||||
CantOpen(e) => Some(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ImageError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use ImageError::*;
|
||||
match self {
|
||||
TooBig => writeln!(
|
||||
f,
|
||||
"The given image is too big. DBus only has 32 bits for width / height"
|
||||
),
|
||||
WrongDataSize => writeln!(
|
||||
f,
|
||||
"The given bytes don't match the width, height and channel count"
|
||||
),
|
||||
CantOpen(e) => writeln!(f, "Can't open given path {}", e),
|
||||
CantConvert => writeln!(f, "Can't convert from given input"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// matching image data key for each spec version
|
||||
#[cfg(feature = "dbus")]
|
||||
pub(crate) fn image_spec(version: Version) -> String {
|
||||
match version.cmp(&Version::new(1, 1)) {
|
||||
Ordering::Less => constants::IMAGE_DATA_1_0.to_owned(),
|
||||
Ordering::Equal => constants::IMAGE_DATA_1_1.to_owned(),
|
||||
Ordering::Greater => constants::IMAGE_DATA.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// matching image data key for each spec version
|
||||
#[cfg(feature = "zbus")]
|
||||
pub(crate) fn image_spec_str(version: Version) -> &'static str {
|
||||
match version.cmp(&Version::new(1, 1)) {
|
||||
Ordering::Less => constants::IMAGE_DATA_1_0,
|
||||
Ordering::Equal => constants::IMAGE_DATA_1_1,
|
||||
Ordering::Greater => constants::IMAGE_DATA,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dbus")]
|
||||
pub struct ImageMessage(Image);
|
||||
|
||||
#[cfg(feature = "dbus")]
|
||||
impl From<Image> for ImageMessage {
|
||||
fn from(hint: Image) -> Self {
|
||||
ImageMessage(hint)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<image::ImageError> for ImageError {
|
||||
fn from(image_error: image::ImageError) -> Self {
|
||||
ImageError::CantOpen(image_error)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dbus")]
|
||||
impl std::ops::Deref for ImageMessage {
|
||||
type Target = Image;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dbus")]
|
||||
impl From<ImageMessage> for MessageItem {
|
||||
fn from(img_msg: ImageMessage) -> Self {
|
||||
let img = img_msg.0;
|
||||
|
||||
let bytes = img.data.into_iter().map(MessageItem::Byte).collect();
|
||||
|
||||
MessageItem::Struct(vec![
|
||||
MessageItem::Int32(img.width),
|
||||
MessageItem::Int32(img.height),
|
||||
MessageItem::Int32(img.rowstride),
|
||||
MessageItem::Bool(img.alpha),
|
||||
MessageItem::Int32(img.bits_per_sample),
|
||||
MessageItem::Int32(img.channels),
|
||||
MessageItem::Array(MessageItemArray::new(bytes, "ay".into()).unwrap()),
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
use super::{error::*, notification::Notification};
|
||||
|
||||
pub use mac_notification_sys::error::{ApplicationError, Error as MacOsError, NotificationError};
|
||||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// A handle to a shown notification.
|
||||
///
|
||||
/// This keeps a connection alive to ensure actions work on certain desktops.
|
||||
#[derive(Debug)]
|
||||
pub struct NotificationHandle {
|
||||
notification: Notification,
|
||||
}
|
||||
|
||||
impl NotificationHandle {
|
||||
#[allow(missing_docs)]
|
||||
pub fn new(notification: Notification) -> NotificationHandle {
|
||||
NotificationHandle { notification }
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for NotificationHandle {
|
||||
type Target = Notification;
|
||||
|
||||
fn deref(&self) -> &Notification {
|
||||
&self.notification
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow to easily modify notification properties
|
||||
impl DerefMut for NotificationHandle {
|
||||
fn deref_mut(&mut self) -> &mut Notification {
|
||||
&mut self.notification
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
|
||||
mac_notification_sys::Notification::default()
|
||||
.title(notification.summary.as_str())
|
||||
.message(¬ification.body)
|
||||
.maybe_subtitle(notification.subtitle.as_deref())
|
||||
.maybe_sound(notification.sound_name.as_deref())
|
||||
.send()?;
|
||||
|
||||
Ok(NotificationHandle::new(notification.clone()))
|
||||
}
|
||||
|
||||
pub(crate) fn schedule_notification(
|
||||
notification: &Notification,
|
||||
delivery_date: f64,
|
||||
) -> Result<NotificationHandle> {
|
||||
mac_notification_sys::Notification::default()
|
||||
.title(notification.summary.as_str())
|
||||
.message(¬ification.body)
|
||||
.maybe_subtitle(notification.subtitle.as_deref())
|
||||
.maybe_sound(notification.sound_name.as_deref())
|
||||
.delivery_date(delivery_date)
|
||||
.send()?;
|
||||
|
||||
Ok(NotificationHandle::new(notification.clone()))
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
use super::error::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Copy, Clone, Eq, Debug)]
|
||||
pub struct Version {
|
||||
pub major: u64,
|
||||
pub minor: u64,
|
||||
}
|
||||
|
||||
impl Version {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(major: u64, minor: u64) -> Self {
|
||||
Self { major, minor }
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Version {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Version> {
|
||||
let vv = s.split('.').collect::<Vec<&str>>();
|
||||
match (vv.first(), vv.get(1)) {
|
||||
(Some(maj), Some(min)) => Ok(Version {
|
||||
major: maj.parse()?,
|
||||
minor: min.parse()?,
|
||||
}),
|
||||
_ => Err(ErrorKind::SpecVersion(s.into()).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use std::cmp;
|
||||
|
||||
impl PartialOrd for Version {
|
||||
fn partial_cmp(&self, other: &Version) -> Option<cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Version {
|
||||
fn eq(&self, other: &Version) -> bool {
|
||||
self.major == other.major && self.minor == other.minor
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Version {
|
||||
fn cmp(&self, other: &Version) -> cmp::Ordering {
|
||||
match self.major.cmp(&other.major) {
|
||||
cmp::Ordering::Equal => {}
|
||||
r => return r,
|
||||
}
|
||||
match self.minor.cmp(&other.minor) {
|
||||
cmp::Ordering::Equal => {}
|
||||
r => return r,
|
||||
}
|
||||
cmp::Ordering::Equal
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn version_parsing() {
|
||||
assert_eq!("1.3".parse::<Version>().unwrap(), Version::new(1, 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_comparison() {
|
||||
assert!(Version::new(1, 3) >= Version::new(1, 2));
|
||||
assert!(Version::new(1, 2) >= Version::new(1, 2));
|
||||
assert!(Version::new(1, 2) == Version::new(1, 2));
|
||||
assert!(Version::new(1, 1) <= Version::new(1, 2));
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
//! Desktop Notifications for Rust.
|
||||
//!
|
||||
//! Desktop notifications are popup messages generated to notify the user of certain events.
|
||||
//!
|
||||
//! ## Platform Support
|
||||
//!
|
||||
//! This library was originally conceived with the [XDG](https://en.wikipedia.org/wiki/XDG) notification specification in mind.
|
||||
//! Since version 3.3 this crate also builds on macOS, however the semantics of the [XDG](https://en.wikipedia.org/wiki/XDG) specification and macOS `NSNotifications`
|
||||
//! are quite different.
|
||||
//! Therefore only a very small subset of functions is supported on macOS.
|
||||
//! Certain methods don't have any effect there, others will explicitly fail to compile,
|
||||
//! in these cases you will have to add platform specific toggles to your code.
|
||||
//! For more see [platform differences](#platform-differences)
|
||||
//!
|
||||
//! # Platform Differences
|
||||
//! <details>
|
||||
//! ✔︎ = works <br/>
|
||||
//! ❌ = will not compile
|
||||
//!
|
||||
//! ## `Notification`
|
||||
//! | method | XDG | macOS | windows |
|
||||
//! |---------------------|-------|-------|---------|
|
||||
//! | `fn appname(...)` | ✔︎ | | |
|
||||
//! | `fn summary(...)` | ✔︎ | ✔︎ | ✔︎ |
|
||||
//! | `fn subtitle(...)` | | ✔︎ | ✔︎ |
|
||||
//! | `fn body(...)` | ✔︎ | ✔︎ | ✔︎ |
|
||||
//! | `fn icon(...)` | ✔︎ | | |
|
||||
//! | `fn auto_icon(...)`| ✔︎ | | |
|
||||
//! | `fn hint(...)` | ✔︎ | ❌ | ❌ |
|
||||
//! | `fn timeout(...)` | ✔︎ | | ✔︎ |
|
||||
//! | `fn urgency(...)` | ✔︎ | ❌ | ❌ |
|
||||
//! | `fn action(...)` | ✔︎ | | |
|
||||
//! | `fn id(...)` | ✔︎ | | |
|
||||
//! | `fn finalize(...)` | ✔︎ | ✔︎ | ✔︎ |
|
||||
//! | `fn show(...)` | ✔︎ | ✔︎ | ✔︎ |
|
||||
//!
|
||||
//! ## `NotificationHandle`
|
||||
//!
|
||||
//! | method | XDG | macOS | windows |
|
||||
//! |--------------------------|-----|-------|---------|
|
||||
//! | `fn wait_for_action(...)`| ✔︎ | ❌ | ❌ |
|
||||
//! | `fn close(...)` | ✔︎ | ❌ | ❌ |
|
||||
//! | `fn on_close(...)` | ✔︎ | ❌ | ❌ |
|
||||
//! | `fn update(...)` | ✔︎ | ❌ | ❌ |
|
||||
//! | `fn id(...)` | ✔︎ | ❌ | ❌ |
|
||||
//!
|
||||
//! ## Functions
|
||||
//!
|
||||
//! | | XDG | macOS | windows |
|
||||
//! |--------------------------------------------|-----|-------|---------|
|
||||
//! | `fn get_capabilities(...)` | ✔︎ | ❌ | ❌ |
|
||||
//! | `fn get_server_information(...)` | ✔︎ | ❌ | ❌ |
|
||||
//! | `fn set_application(...)` | ❌ | ✔︎ | ❌ |
|
||||
//! | `fn get_bundle_identifier_or_default(...)` | ❌ | ✔︎ | ❌ |
|
||||
//!
|
||||
//!
|
||||
//! ### Toggles
|
||||
//!
|
||||
//! Please use `target_os` toggles if you plan on using methods labeled with ❌.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! #[cfg(target_os = "macos")]
|
||||
//! // or
|
||||
//! // #### #[cfg(all(unix, not(target_os = "macos")))]
|
||||
//! ```
|
||||
//! </details>
|
||||
//!
|
||||
|
||||
#![deny(
|
||||
missing_copy_implementations,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unsafe_code,
|
||||
unused_import_braces,
|
||||
unused_qualifications
|
||||
)]
|
||||
#![warn(
|
||||
missing_docs,
|
||||
clippy::doc_markdown,
|
||||
clippy::semicolon_if_nothing_returned,
|
||||
clippy::single_match_else,
|
||||
clippy::inconsistent_struct_constructor,
|
||||
clippy::map_unwrap_or,
|
||||
clippy::match_same_arms
|
||||
)]
|
||||
|
||||
#[cfg(all(feature = "dbus", unix, not(target_os = "macos")))]
|
||||
extern crate dbus;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
extern crate mac_notification_sys;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate winrt_notification;
|
||||
|
||||
#[macro_use]
|
||||
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
|
||||
extern crate lazy_static;
|
||||
|
||||
pub mod error;
|
||||
mod hints;
|
||||
mod miniver;
|
||||
mod notification;
|
||||
mod timeout;
|
||||
pub(crate) mod urgency;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows;
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
mod xdg;
|
||||
|
||||
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
|
||||
mod image;
|
||||
|
||||
#[cfg(all(feature = "server", feature = "dbus", unix, not(target_os = "macos")))]
|
||||
pub mod server;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use mac_notification_sys::{get_bundle_identifier_or_default, set_application};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use macos::NotificationHandle;
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "dbus", feature = "zbus"),
|
||||
unix,
|
||||
not(target_os = "macos")
|
||||
))]
|
||||
pub use xdg::{
|
||||
dbus_stack, get_capabilities, get_server_information, handle_action, ActionResponse,
|
||||
CloseHandler, CloseReason, DbusStack, NotificationHandle,
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "server", unix, not(target_os = "macos")))]
|
||||
pub use xdg::stop_server;
|
||||
|
||||
pub use hints::Hint;
|
||||
|
||||
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
|
||||
pub use image::{Image, ImageError};
|
||||
|
||||
#[cfg_attr(
|
||||
target_os = "macos",
|
||||
deprecated(note = "Urgency is not supported on macOS")
|
||||
)]
|
||||
pub use urgency::Urgency;
|
||||
|
||||
pub use {notification::Notification, timeout::Timeout};
|
||||
|
||||
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
|
||||
lazy_static! {
|
||||
/// Read once at runtime. Needed for Images
|
||||
pub static ref SPEC_VERSION: miniver::Version =
|
||||
get_server_information()
|
||||
.and_then(|info| info.spec_version.parse::<miniver::Version>())
|
||||
.unwrap_or_else(|_| miniver::Version::new(1,1));
|
||||
}
|
||||
/// Return value of `get_server_information()`.
|
||||
#[derive(Debug)]
|
||||
pub struct ServerInformation {
|
||||
/// The product name of the server.
|
||||
pub name: String,
|
||||
/// The vendor name.
|
||||
pub vendor: String,
|
||||
/// The server's version string.
|
||||
pub version: String,
|
||||
/// The specification version the server is compliant with.
|
||||
pub spec_version: String,
|
||||
}
|
||||
@@ -1,480 +0,0 @@
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
use super::{
|
||||
hints::{CustomHintType, Hint},
|
||||
urgency::Urgency,
|
||||
xdg,
|
||||
};
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos"), feature = "images"))]
|
||||
use super::image::Image;
|
||||
|
||||
#[cfg(all(unix, target_os = "macos"))]
|
||||
use super::macos;
|
||||
#[cfg(target_os = "windows")]
|
||||
use super::windows;
|
||||
|
||||
use super::{error::*, timeout::Timeout};
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
// Returns the name of the current executable, used as a default for `Notification.appname`.
|
||||
fn exe_name() -> String {
|
||||
std::env::current_exe()
|
||||
.unwrap()
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
/// Desktop notification.
|
||||
///
|
||||
/// A desktop notification is configured via builder pattern, before it is launched with `show()`.
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct Notification {
|
||||
/// Filled by default with executable name.
|
||||
pub appname: String,
|
||||
|
||||
/// Single line to summarize the content.
|
||||
pub summary: String,
|
||||
|
||||
/// Subtitle for macOS
|
||||
pub subtitle: Option<String>,
|
||||
|
||||
/// Multiple lines possible, may support simple markup,
|
||||
/// check out `get_capabilities()` -> `body-markup` and `body-hyperlinks`.
|
||||
pub body: String,
|
||||
|
||||
/// Use a file:// URI or a name in an icon theme, must be compliant freedesktop.org.
|
||||
pub icon: String,
|
||||
|
||||
/// Check out `Hint`
|
||||
///
|
||||
/// # warning
|
||||
/// this does not hold all hints, [`Hint::Custom`] and [`Hint::CustomInt`] are held elsewhere,
|
||||
// /// please access hints via [`Notification::get_hints`].
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub hints: HashSet<Hint>,
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub(crate) hints_unique: HashMap<(String, CustomHintType), Hint>,
|
||||
|
||||
/// See `Notification::actions()` and `Notification::action()`
|
||||
pub actions: Vec<String>,
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) sound_name: Option<String>,
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) sound_name: Option<String>,
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) path_to_image: Option<String>,
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) app_id: Option<String>,
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub(crate) bus: xdg::NotificationBus,
|
||||
|
||||
/// Lifetime of the Notification in ms. Often not respected by server, sorry.
|
||||
pub timeout: Timeout, // both gnome and galago want allow for -1
|
||||
|
||||
/// Only to be used on the receive end. Use Notification hand for updating.
|
||||
pub(crate) id: Option<u32>,
|
||||
}
|
||||
|
||||
impl Notification {
|
||||
/// Constructs a new Notification.
|
||||
///
|
||||
/// Most fields are empty by default, only `appname` is initialized with the name of the current
|
||||
/// executable.
|
||||
/// The appname is used by some desktop environments to group notifications.
|
||||
pub fn new() -> Notification {
|
||||
Notification::default()
|
||||
}
|
||||
|
||||
/// This is for testing purposes only and will not work with actual implementations.
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
#[doc(hidden)]
|
||||
#[deprecated(note = "this is a test only feature")]
|
||||
pub fn at_bus(sub_bus: &str) -> Notification {
|
||||
let bus = xdg::NotificationBus::custom(sub_bus)
|
||||
.ok_or("invalid subpath")
|
||||
.unwrap();
|
||||
Notification {
|
||||
bus,
|
||||
..Notification::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Overwrite the appname field used for Notification.
|
||||
///
|
||||
/// # Platform Support
|
||||
/// Please note that this method has no effect on macOS. Here you can only set the application via [`set_application()`](fn.set_application.html)
|
||||
pub fn appname(&mut self, appname: &str) -> &mut Notification {
|
||||
appname.clone_into(&mut self.appname);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `summary`.
|
||||
///
|
||||
/// Often acts as title of the notification. For more elaborate content use the `body` field.
|
||||
pub fn summary(&mut self, summary: &str) -> &mut Notification {
|
||||
summary.clone_into(&mut self.summary);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `subtitle`.
|
||||
///
|
||||
/// This is only useful on macOS, it's not part of the XDG specification and will therefore be eaten by gremlins under your CPU 😈🤘.
|
||||
pub fn subtitle(&mut self, subtitle: &str) -> &mut Notification {
|
||||
self.subtitle = Some(subtitle.to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Manual wrapper for `Hint::ImageData`
|
||||
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
|
||||
pub fn image_data(&mut self, image: Image) -> &mut Notification {
|
||||
self.hint(Hint::ImageData(image));
|
||||
self
|
||||
}
|
||||
|
||||
/// Wrapper for `Hint::ImagePath`
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub fn image_path(&mut self, path: &str) -> &mut Notification {
|
||||
self.hint(Hint::ImagePath(path.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Wrapper for `NotificationHint::ImagePath`
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn image_path(&mut self, path: &str) -> &mut Notification {
|
||||
self.path_to_image = Some(path.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// app's System.AppUserModel.ID
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn app_id(&mut self, app_id: &str) -> &mut Notification {
|
||||
self.app_id = Some(app_id.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Wrapper for `Hint::ImageData`
|
||||
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
|
||||
pub fn image<T: AsRef<std::path::Path> + Sized>(
|
||||
&mut self,
|
||||
path: T,
|
||||
) -> Result<&mut Notification> {
|
||||
let img = Image::open(&path)?;
|
||||
self.hint(Hint::ImageData(img));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Wrapper for `Hint::SoundName`
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub fn sound_name(&mut self, name: &str) -> &mut Notification {
|
||||
self.hint(Hint::SoundName(name.to_owned()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `sound_name` for the `NSUserNotification`
|
||||
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||
pub fn sound_name(&mut self, name: &str) -> &mut Notification {
|
||||
self.sound_name = Some(name.to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the content of the `body` field.
|
||||
///
|
||||
/// Multiline textual content of the notification.
|
||||
/// Each line should be treated as a paragraph.
|
||||
/// Simple html markup should be supported, depending on the server implementation.
|
||||
pub fn body(&mut self, body: &str) -> &mut Notification {
|
||||
body.clone_into(&mut self.body);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `icon` field.
|
||||
///
|
||||
/// You can use common icon names here, usually those in `/usr/share/icons`
|
||||
/// can all be used.
|
||||
/// You can also use an absolute path to file.
|
||||
///
|
||||
/// # Platform support
|
||||
/// macOS does not have support manually setting the icon. However you can pretend to be another app using [`set_application()`](fn.set_application.html)
|
||||
pub fn icon(&mut self, icon: &str) -> &mut Notification {
|
||||
icon.clone_into(&mut self.icon);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `icon` field automatically.
|
||||
///
|
||||
/// This looks at your binary's name and uses it to set the icon.
|
||||
///
|
||||
/// # Platform support
|
||||
/// macOS does not support manually setting the icon. However you can pretend to be another app using [`set_application()`](fn.set_application.html)
|
||||
pub fn auto_icon(&mut self) -> &mut Notification {
|
||||
self.icon = exe_name();
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a hint.
|
||||
///
|
||||
/// This method will add a hint to the internal hint [`HashSet`].
|
||||
/// Hints must be of type [`Hint`].
|
||||
///
|
||||
/// Many of these are again wrapped by more convenient functions such as:
|
||||
///
|
||||
/// * `sound_name(...)`
|
||||
/// * `urgency(...)`
|
||||
/// * [`image(...)`](#method.image) or
|
||||
/// * [`image_data(...)`](#method.image_data)
|
||||
/// * [`image_path(...)`](#method.image_path)
|
||||
///
|
||||
/// # Platform support
|
||||
/// Most of these hints don't even have an effect on the big XDG Desktops, they are completely tossed on macOS.
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub fn hint(&mut self, hint: Hint) -> &mut Notification {
|
||||
match hint {
|
||||
Hint::CustomInt(k, v) => {
|
||||
self.hints_unique
|
||||
.insert((k.clone(), CustomHintType::Int), Hint::CustomInt(k, v));
|
||||
}
|
||||
Hint::Custom(k, v) => {
|
||||
self.hints_unique
|
||||
.insert((k.clone(), CustomHintType::String), Hint::Custom(k, v));
|
||||
}
|
||||
_ => {
|
||||
self.hints.insert(hint);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub(crate) fn get_hints(&self) -> impl Iterator<Item = &Hint> {
|
||||
self.hints.iter().chain(self.hints_unique.values())
|
||||
}
|
||||
|
||||
/// Set the `timeout`.
|
||||
///
|
||||
/// Accepts multiple types that implement `Into<Timeout>`.
|
||||
///
|
||||
/// ## `i31`
|
||||
///
|
||||
/// This sets the time (in milliseconds) from the time the notification is displayed until it is
|
||||
/// closed again by the Notification Server.
|
||||
/// According to [specification](https://developer.gnome.org/notification-spec/)
|
||||
/// -1 will leave the timeout to be set by the server and
|
||||
/// 0 will cause the notification never to expire.
|
||||
|
||||
/// ## [Duration](`std::time::Duration`)
|
||||
///
|
||||
/// When passing a [`Duration`](`std::time::Duration`) we will try convert it into milliseconds.
|
||||
///
|
||||
/// # Platform support
|
||||
/// This only works on XDG Desktops, macOS does not support manually setting the timeout.
|
||||
pub fn timeout<T: Into<Timeout>>(&mut self, timeout: T) -> &mut Notification {
|
||||
self.timeout = timeout.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `urgency`.
|
||||
///
|
||||
/// Pick between Medium, Low and High.
|
||||
///
|
||||
/// # Platform support
|
||||
/// Most Desktops on linux and bsd are far too relaxed to pay any attention to this.
|
||||
/// In macOS this does not exist
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub fn urgency(&mut self, urgency: Urgency) -> &mut Notification {
|
||||
self.hint(Hint::Urgency(urgency)); // TODO impl as T where T: Into<Urgency>
|
||||
self
|
||||
}
|
||||
|
||||
/// Set `actions`.
|
||||
///
|
||||
/// To quote <http://www.galago-project.org/specs/notification/0.9/x408.html#command-notify>
|
||||
///
|
||||
/// > Actions are sent over as a list of pairs.
|
||||
/// > Each even element in the list (starting at index 0) represents the identifier for the action.
|
||||
/// > Each odd element in the list is the localized string that will be displayed to the user.y
|
||||
///
|
||||
/// There is nothing fancy going on here yet.
|
||||
/// **Careful! This replaces the internal list of actions!**
|
||||
///
|
||||
/// (xdg only)
|
||||
#[deprecated(note = "please use .action() only")]
|
||||
pub fn actions(&mut self, actions: Vec<String>) -> &mut Notification {
|
||||
self.actions = actions;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an action.
|
||||
///
|
||||
/// This adds a single action to the internal list of actions.
|
||||
///
|
||||
/// (xdg only)
|
||||
pub fn action(&mut self, identifier: &str, label: &str) -> &mut Notification {
|
||||
self.actions.push(identifier.to_owned());
|
||||
self.actions.push(label.to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set an Id ahead of time
|
||||
///
|
||||
/// Setting the id ahead of time allows overriding a known other notification.
|
||||
/// Though if you want to update a notification, it is easier to use the `update()` method of
|
||||
/// the `NotificationHandle` object that `show()` returns.
|
||||
///
|
||||
/// (xdg only)
|
||||
pub fn id(&mut self, id: u32) -> &mut Notification {
|
||||
self.id = Some(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Finalizes a Notification.
|
||||
///
|
||||
/// Part of the builder pattern, returns a complete copy of the built notification.
|
||||
pub fn finalize(&self) -> Notification {
|
||||
self.clone()
|
||||
}
|
||||
|
||||
/// Schedules a Notification
|
||||
///
|
||||
/// Sends a Notification at the specified date.
|
||||
#[cfg(all(target_os = "macos", feature = "chrono"))]
|
||||
pub fn schedule<T: chrono::TimeZone>(
|
||||
&self,
|
||||
delivery_date: chrono::DateTime<T>,
|
||||
) -> Result<macos::NotificationHandle> {
|
||||
macos::schedule_notification(self, delivery_date.timestamp() as f64)
|
||||
}
|
||||
|
||||
/// Schedules a Notification
|
||||
///
|
||||
/// Sends a Notification at the specified timestamp.
|
||||
/// This is a raw `f64`, if that is a bit too raw for you please activate the feature `"chrono"`,
|
||||
/// then you can use `Notification::schedule()` instead, which accepts a `chrono::DateTime<T>`.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn schedule_raw(&self, timestamp: f64) -> Result<macos::NotificationHandle> {
|
||||
macos::schedule_notification(self, timestamp)
|
||||
}
|
||||
|
||||
/// Sends Notification to D-Bus.
|
||||
///
|
||||
/// Returns a handle to a notification
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub fn show(&self) -> Result<xdg::NotificationHandle> {
|
||||
xdg::show_notification(self)
|
||||
}
|
||||
|
||||
/// Sends Notification to D-Bus.
|
||||
///
|
||||
/// Returns a handle to a notification
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
#[cfg(all(feature = "async", feature = "zbus"))]
|
||||
pub async fn show_async(&self) -> Result<xdg::NotificationHandle> {
|
||||
xdg::show_notification_async(self).await
|
||||
}
|
||||
|
||||
/// Sends Notification to D-Bus.
|
||||
///
|
||||
/// Returns a handle to a notification
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
#[cfg(feature = "async")]
|
||||
// #[cfg(test)]
|
||||
pub async fn show_async_at_bus(&self, sub_bus: &str) -> Result<xdg::NotificationHandle> {
|
||||
let bus = xdg::NotificationBus::custom(sub_bus).ok_or("invalid subpath")?;
|
||||
xdg::show_notification_async_at_bus(self, bus).await
|
||||
}
|
||||
|
||||
/// Sends Notification to `NSUserNotificationCenter`.
|
||||
///
|
||||
/// Returns an `Ok` no matter what, since there is currently no way of telling the success of
|
||||
/// the notification.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn show(&self) -> Result<macos::NotificationHandle> {
|
||||
macos::show_notification(self)
|
||||
}
|
||||
|
||||
/// Sends Notification to `NSUserNotificationCenter`.
|
||||
///
|
||||
/// Returns an `Ok` no matter what, since there is currently no way of telling the success of
|
||||
/// the notification.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn show(&self) -> Result<()> {
|
||||
windows::show_notification(self)
|
||||
}
|
||||
|
||||
/// Wraps `show()` but prints notification to stdout.
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
#[deprecated = "this was never meant to be public API"]
|
||||
pub fn show_debug(&mut self) -> Result<xdg::NotificationHandle> {
|
||||
println!(
|
||||
"Notification:\n{appname}: ({icon}) {summary:?} {body:?}\nhints: [{hints:?}]\n",
|
||||
appname = self.appname,
|
||||
summary = self.summary,
|
||||
body = self.body,
|
||||
hints = self.hints,
|
||||
icon = self.icon,
|
||||
);
|
||||
self.show()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Notification {
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
fn default() -> Notification {
|
||||
Notification {
|
||||
appname: exe_name(),
|
||||
summary: String::new(),
|
||||
subtitle: None,
|
||||
body: String::new(),
|
||||
icon: String::new(),
|
||||
hints: HashSet::new(),
|
||||
hints_unique: HashMap::new(),
|
||||
actions: Vec::new(),
|
||||
timeout: Timeout::Default,
|
||||
bus: Default::default(),
|
||||
id: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn default() -> Notification {
|
||||
Notification {
|
||||
appname: exe_name(),
|
||||
summary: String::new(),
|
||||
subtitle: None,
|
||||
body: String::new(),
|
||||
icon: String::new(),
|
||||
actions: Vec::new(),
|
||||
timeout: Timeout::Default,
|
||||
sound_name: Default::default(),
|
||||
id: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn default() -> Notification {
|
||||
Notification {
|
||||
appname: exe_name(),
|
||||
summary: String::new(),
|
||||
subtitle: None,
|
||||
body: String::new(),
|
||||
icon: String::new(),
|
||||
actions: Vec::new(),
|
||||
timeout: Timeout::Default,
|
||||
sound_name: Default::default(),
|
||||
id: None,
|
||||
path_to_image: None,
|
||||
app_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
//! **Experimental** server taking the place of your Desktop Environment's Notification Server.
|
||||
//!
|
||||
//! This is not nearly meant for anything but testing, as it only prints notifications to stdout.
|
||||
//! It does not respond properly either yet.
|
||||
//!
|
||||
//! This server will not replace an already running notification server.
|
||||
//!
|
||||
|
||||
#![allow(unused_imports, unused_variables, dead_code)]
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[cfg(feature = "dbus")]
|
||||
use dbus::{
|
||||
arg::{self, RefArg},
|
||||
ffidisp::{BusType, Connection, NameFlag},
|
||||
tree::{self, Factory, Interface, MTFn, MTSync, Tree},
|
||||
Path,
|
||||
};
|
||||
|
||||
use super::xdg::{NOTIFICATION_NAMESPACE, NOTIFICATION_OBJECTPATH};
|
||||
use super::{Hint, Notification, Timeout};
|
||||
|
||||
static DBUS_ERROR_FAILED: &str = "org.freedesktop.DBus.Error.Failed";
|
||||
/// Version of the crate equals the version server.
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
/// An **experimental** notification server.
|
||||
/// See [the module level documentation](index.html) for more.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct NotificationServer {
|
||||
/// Counter for generating notification ids
|
||||
counter: Mutex<Cell<u32>>,
|
||||
|
||||
/// A flag that stops the server
|
||||
stopped: Mutex<Cell<bool>>,
|
||||
}
|
||||
|
||||
impl NotificationServer {
|
||||
fn count_up(&self) {
|
||||
if let Ok(counter) = self.counter.lock() {
|
||||
counter.set(counter.get() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
if let Ok(stop) = self.stopped.lock() {
|
||||
stop.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_stopped(&self) -> bool {
|
||||
if let Ok(stop) = self.stopped.lock() {
|
||||
stop.get()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `NotificationServer` instance.
|
||||
pub fn create() -> Arc<NotificationServer> {
|
||||
Arc::new(NotificationServer::default())
|
||||
}
|
||||
// pub fn notify_mothod<F>(&mut self, closure: F)
|
||||
// -> Method
|
||||
// where F: Fn(&Notification)
|
||||
// {
|
||||
|
||||
// fn handle_notification
|
||||
|
||||
/// Start listening for incoming notifications
|
||||
pub fn start<F: 'static>(me: &Arc<Self>, closure: F)
|
||||
where
|
||||
F: Fn(&Notification),
|
||||
{
|
||||
let connection = Connection::get_private(BusType::Session).unwrap();
|
||||
|
||||
connection.release_name(NOTIFICATION_NAMESPACE).unwrap();
|
||||
connection
|
||||
.register_name(NOTIFICATION_NAMESPACE, NameFlag::ReplaceExisting as u32)
|
||||
.unwrap();
|
||||
connection
|
||||
.register_object_path(NOTIFICATION_OBJECTPATH)
|
||||
.unwrap();
|
||||
|
||||
let mytex = Arc::new(Mutex::new(me.clone()));
|
||||
|
||||
let factory = Factory::new_fn::<()>(); // D::Tree = ()
|
||||
let tree = factory.tree(()).add(
|
||||
factory
|
||||
.object_path(NOTIFICATION_OBJECTPATH, ())
|
||||
.introspectable()
|
||||
.add(
|
||||
factory
|
||||
.interface(NOTIFICATION_NAMESPACE, ())
|
||||
.add_m(method_notify(&factory, closure))
|
||||
.add_m(method_close_notification(&factory))
|
||||
.add_m(Self::stop_server(mytex.clone(), &factory))
|
||||
// .add_signal(method_notification_closed(&factory))
|
||||
// .add_signal(method_action_invoked(&factory))
|
||||
.add_m(method_get_capabilities(&factory))
|
||||
.add_m(method_get_server_information(&factory)),
|
||||
),
|
||||
);
|
||||
|
||||
connection.add_handler(tree);
|
||||
|
||||
while !me.is_stopped() {
|
||||
// Wait for incoming messages. This will block up to one second.
|
||||
// Discard the result - relevant messages have already been handled.
|
||||
if let Some(received) = connection.incoming(1000).next() {
|
||||
println!("RECEIVED {:?}", received);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_server(
|
||||
me: Arc<Mutex<Arc<Self>>>,
|
||||
factory: &Factory<MTFn>,
|
||||
) -> tree::Method<MTFn<()>, ()> {
|
||||
factory
|
||||
.method("Stop", (), move |minfo| {
|
||||
if let Ok(me) = me.lock() {
|
||||
me.stop();
|
||||
println!("STOPPING");
|
||||
Ok(vec![])
|
||||
} else {
|
||||
Err(tree::MethodErr::failed(&String::from("nope!")))
|
||||
}
|
||||
})
|
||||
.out_arg(("", "u"))
|
||||
}
|
||||
}
|
||||
|
||||
fn hints_from_variants<A: RefArg>(hints: &HashMap<String, A>) -> HashSet<Hint> {
|
||||
hints.iter().map(Into::into).collect()
|
||||
}
|
||||
|
||||
fn method_notify<F: 'static>(
|
||||
factory: &Factory<MTFn>,
|
||||
on_notification: F,
|
||||
) -> tree::Method<MTFn<()>, ()>
|
||||
where
|
||||
F: Fn(&Notification),
|
||||
{
|
||||
factory
|
||||
.method("Notify", (), move |minfo| {
|
||||
let mut i = minfo.msg.iter_init();
|
||||
let appname: String = i.read()?;
|
||||
let replaces_id: u32 = i.read()?;
|
||||
let icon: String = i.read()?;
|
||||
let summary: String = i.read()?;
|
||||
let body: String = i.read()?;
|
||||
let actions: Vec<String> = i.read()?;
|
||||
let hints: ::std::collections::HashMap<String, arg::Variant<Box<dyn RefArg>>> =
|
||||
i.read()?;
|
||||
let timeout: i32 = i.read()?;
|
||||
println!("hints {:?} ", hints);
|
||||
|
||||
// let arg0 = try!(d.notify(app_name, replaces_id, app_icon, summary, body, actions, hints, timeout));
|
||||
let notification = Notification {
|
||||
appname,
|
||||
icon,
|
||||
summary,
|
||||
body,
|
||||
actions,
|
||||
hints: hints_from_variants(&hints),
|
||||
timeout: Timeout::from(timeout),
|
||||
id: if replaces_id == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(replaces_id)
|
||||
},
|
||||
subtitle: None,
|
||||
};
|
||||
|
||||
on_notification(¬ification);
|
||||
|
||||
let arg0 = 43;
|
||||
let rm = minfo.msg.method_return();
|
||||
let rm = rm.append1(arg0);
|
||||
Ok(vec![rm])
|
||||
})
|
||||
.in_arg(("app_name", "s"))
|
||||
.in_arg(("replaces_id", "u"))
|
||||
.in_arg(("app_icon", "s"))
|
||||
.in_arg(("summary", "s"))
|
||||
.in_arg(("body", "s"))
|
||||
.in_arg(("actions", "as"))
|
||||
.in_arg(("hints", "a{sv}"))
|
||||
.in_arg(("timeout", "i"))
|
||||
.out_arg(("", "u"))
|
||||
}
|
||||
|
||||
fn method_close_notification(factory: &Factory<MTFn>) -> tree::Method<MTFn<()>, ()> {
|
||||
factory
|
||||
.method("CloseNotification", (), |minfo| {
|
||||
let i = minfo.msg.iter_init();
|
||||
let rm = minfo.msg.method_return();
|
||||
Ok(vec![rm])
|
||||
})
|
||||
.in_arg(("id", "u"))
|
||||
}
|
||||
|
||||
fn method_get_capabilities(factory: &Factory<MTFn>) -> tree::Method<MTFn<()>, ()> {
|
||||
factory
|
||||
.method("GetCapabilities", (), |minfo| {
|
||||
let caps: Vec<String> = vec![];
|
||||
let rm = minfo.msg.method_return();
|
||||
let rm = rm.append1(caps);
|
||||
Ok(vec![rm])
|
||||
})
|
||||
.out_arg(("caps", "as"))
|
||||
}
|
||||
|
||||
fn method_get_server_information(factory: &Factory<MTFn>) -> tree::Method<MTFn<()>, ()> {
|
||||
factory
|
||||
.method("GetServerInformation", (), |minfo| {
|
||||
let (name, vendor, version, spec_version) = (
|
||||
"notify-rust",
|
||||
"notify-rust",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
"0.0.0",
|
||||
);
|
||||
let rm = minfo.msg.method_return();
|
||||
let rm = rm.append1(name);
|
||||
let rm = rm.append1(vendor);
|
||||
let rm = rm.append1(version);
|
||||
let rm = rm.append1(spec_version);
|
||||
Ok(vec![rm])
|
||||
})
|
||||
.out_arg(("name", "s"))
|
||||
.out_arg(("vendor", "s"))
|
||||
.out_arg(("version", "s"))
|
||||
.out_arg(("spec_version", "s"))
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
use std::{convert::TryInto, num::ParseIntError, str::FromStr, time::Duration};
|
||||
|
||||
/// Describes the timeout of a notification
|
||||
///
|
||||
/// # `FromStr`
|
||||
/// You can also parse a `Timeout` from a `&str`.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Timeout {
|
||||
/// Expires according to server default.
|
||||
///
|
||||
/// Whatever that might be...
|
||||
Default,
|
||||
|
||||
/// Do not expire, user will have to close this manually.
|
||||
Never,
|
||||
|
||||
/// Expire after n milliseconds.
|
||||
Milliseconds(u32),
|
||||
}
|
||||
|
||||
impl Default for Timeout {
|
||||
fn default() -> Self {
|
||||
Timeout::Default
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timeout_from_i32() {
|
||||
assert_eq!(Timeout::from(234), Timeout::Milliseconds(234));
|
||||
assert_eq!(Timeout::from(-234), Timeout::Default);
|
||||
assert_eq!(Timeout::from(0), Timeout::Never);
|
||||
}
|
||||
|
||||
impl From<i32> for Timeout {
|
||||
fn from(int: i32) -> Timeout {
|
||||
use std::cmp::Ordering::*;
|
||||
match int.cmp(&0) {
|
||||
Greater => Timeout::Milliseconds(int as u32),
|
||||
Less => Timeout::Default,
|
||||
Equal => Timeout::Never,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Duration> for Timeout {
|
||||
fn from(duration: Duration) -> Timeout {
|
||||
if duration.is_zero() {
|
||||
Timeout::Never
|
||||
} else if duration.as_millis() > u32::MAX.into() {
|
||||
Timeout::Default
|
||||
} else {
|
||||
Timeout::Milliseconds(duration.as_millis().try_into().unwrap_or(u32::MAX))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Timeout> for i32 {
|
||||
fn from(timeout: Timeout) -> Self {
|
||||
match timeout {
|
||||
Timeout::Default => -1,
|
||||
Timeout::Never => 0,
|
||||
Timeout::Milliseconds(ms) => ms as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Timeout {
|
||||
type Err = ParseIntError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"default" => Ok(Timeout::Default),
|
||||
"never" => Ok(Timeout::Never),
|
||||
milliseconds => Ok(Timeout::Milliseconds(u32::from_str(milliseconds)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TimeoutMessage(Timeout);
|
||||
|
||||
impl From<Timeout> for TimeoutMessage {
|
||||
fn from(hint: Timeout) -> Self {
|
||||
TimeoutMessage(hint)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for TimeoutMessage {
|
||||
type Target = Timeout;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "dbus", unix, not(target_os = "macos")))]
|
||||
impl TryFrom<&dbus::arg::messageitem::MessageItem> for TimeoutMessage {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(mi: &dbus::arg::messageitem::MessageItem) -> Result<TimeoutMessage, ()> {
|
||||
mi.inner::<i32>().map(|i| TimeoutMessage(i.into()))
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
use super::error::ErrorKind;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
/// Levels of Urgency.
|
||||
///
|
||||
/// # Specification
|
||||
/// > Developers must use their own judgement when deciding the urgency of a notification. Typically, if the majority of programs are using the same level for a specific type of urgency, other applications should follow them.
|
||||
/// >
|
||||
/// > For low and normal urgencies, server implementations may display the notifications how they choose. They should, however, have a sane expiration timeout dependent on the urgency level.
|
||||
/// >
|
||||
/// > **Critical notifications should not automatically expire**, as they are things that the user will most likely want to know about. They should only be closed when the user dismisses them, for example, by clicking on the notification.
|
||||
///
|
||||
/// <cite> — see [Galago](http://www.galago-project.org/specs/notification/0.9/x320.html) or [Gnome](https://developer.gnome.org/notification-spec/#urgency-levels) specification.</cite>
|
||||
#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]
|
||||
pub enum Urgency {
|
||||
/// The behavior for `Low` urgency depends on the notification server.
|
||||
Low = 0,
|
||||
/// The behavior for `Normal` urgency depends on the notification server.
|
||||
Normal = 1,
|
||||
/// A critical notification will not time out.
|
||||
Critical = 2,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Urgency {
|
||||
type Error = super::error::Error;
|
||||
|
||||
#[rustfmt::skip]
|
||||
fn try_from(string: &str) -> Result<Urgency, Self::Error> {
|
||||
match string.to_lowercase().as_ref() {
|
||||
"low" |
|
||||
"lo" => Ok(Urgency::Low),
|
||||
"normal" |
|
||||
"medium" => Ok(Urgency::Normal),
|
||||
"critical" |
|
||||
"high" |
|
||||
"hi" => Ok(Urgency::Critical),
|
||||
_ => Err(ErrorKind::Conversion(format!("invalid input {:?}", string)).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<u64>> for Urgency {
|
||||
fn from(maybe_int: Option<u64>) -> Urgency {
|
||||
match maybe_int {
|
||||
Some(0) => Urgency::Low,
|
||||
Some(x) if x >= 2 => Urgency::Critical,
|
||||
_ => Urgency::Normal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove this in v5.0
|
||||
#[cfg(not(feature = "server"))]
|
||||
impl From<u64> for Urgency {
|
||||
fn from(int: u64) -> Urgency {
|
||||
match int {
|
||||
0 => Urgency::Low,
|
||||
1 => Urgency::Normal,
|
||||
2..=std::u64::MAX => Urgency::Critical,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make this the default in v5.0
|
||||
#[cfg(feature = "server")]
|
||||
impl From<u8> for Urgency {
|
||||
fn from(int: u8) -> Urgency {
|
||||
match int {
|
||||
0 => Urgency::Low,
|
||||
1 => Urgency::Normal,
|
||||
2..=std::u8::MAX => Urgency::Critical,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
use winrt_notification::Toast;
|
||||
|
||||
pub use super::{error::*, notification::Notification, timeout::Timeout};
|
||||
|
||||
use std::{path::Path, str::FromStr};
|
||||
|
||||
pub(crate) fn show_notification(notification: &Notification) -> Result<()> {
|
||||
let sound = match ¬ification.sound_name {
|
||||
Some(chosen_sound_name) => winrt_notification::Sound::from_str(chosen_sound_name).ok(),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let duration = match notification.timeout {
|
||||
Timeout::Default => winrt_notification::Duration::Short,
|
||||
Timeout::Never => winrt_notification::Duration::Long,
|
||||
Timeout::Milliseconds(t) => {
|
||||
if t >= 25000 {
|
||||
winrt_notification::Duration::Long
|
||||
} else {
|
||||
winrt_notification::Duration::Short
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let powershell_app_id = &Toast::POWERSHELL_APP_ID.to_string();
|
||||
let app_id = ¬ification.app_id.as_ref().unwrap_or(powershell_app_id);
|
||||
let mut toast = Toast::new(app_id)
|
||||
.title(¬ification.summary)
|
||||
.text1(notification.subtitle.as_ref().map_or("", AsRef::as_ref)) // subtitle
|
||||
.text2(¬ification.body)
|
||||
.sound(sound)
|
||||
.duration(duration);
|
||||
if let Some(image_path) = ¬ification.path_to_image {
|
||||
toast = toast.image(Path::new(&image_path), "");
|
||||
}
|
||||
|
||||
toast
|
||||
.show()
|
||||
.map_err(|e| Error::from(ErrorKind::Msg(format!("{:?}", e))))
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
use super::super::xdg::NOTIFICATION_DEFAULT_BUS;
|
||||
|
||||
fn skip_first_slash(s: &str) -> &str {
|
||||
if let Some('/') = s.chars().next() {
|
||||
&s[1..]
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
type BusNameType = std::borrow::Cow<'static, str>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NotificationBus(BusNameType);
|
||||
|
||||
impl Default for NotificationBus {
|
||||
#[cfg(feature = "zbus")]
|
||||
fn default() -> Self {
|
||||
Self(
|
||||
zbus::names::WellKnownName::from_static_str(NOTIFICATION_DEFAULT_BUS)
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "dbus", not(feature = "zbus")))]
|
||||
fn default() -> Self {
|
||||
Self(
|
||||
dbus::strings::BusName::from_slice(NOTIFICATION_DEFAULT_BUS)
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl NotificationBus {
|
||||
fn namespaced_custom(custom_path: &str) -> Option<String> {
|
||||
// abusing path for semantic join
|
||||
skip_first_slash(
|
||||
PathBuf::from("/de/hoodie/Notification")
|
||||
.join(custom_path)
|
||||
.to_str()?,
|
||||
)
|
||||
.replace('/', ".")
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(feature = "zbus")]
|
||||
pub fn custom(custom_path: &str) -> Option<Self> {
|
||||
let name =
|
||||
zbus::names::WellKnownName::try_from(Self::namespaced_custom(custom_path)?).ok()?;
|
||||
Some(Self(name.to_string().into()))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "dbus", not(feature = "zbus")))]
|
||||
pub fn custom(custom_path: &str) -> Option<Self> {
|
||||
let name = dbus::strings::BusName::new(Self::namespaced_custom(custom_path)?).ok()?;
|
||||
Some(Self(name.to_string().into()))
|
||||
}
|
||||
|
||||
pub fn into_name(self) -> BusNameType {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
use dbus::{
|
||||
arg::messageitem::{MessageItem, MessageItemArray},
|
||||
ffidisp::{BusType, Connection, ConnectionItem},
|
||||
Message,
|
||||
};
|
||||
|
||||
use super::{
|
||||
bus::NotificationBus, ActionResponse, ActionResponseHandler, CloseReason,
|
||||
NOTIFICATION_INTERFACE,
|
||||
};
|
||||
|
||||
use super::super::{
|
||||
error::*,
|
||||
hints::message::HintMessage,
|
||||
notification::Notification,
|
||||
xdg::{ServerInformation, NOTIFICATION_OBJECTPATH},
|
||||
};
|
||||
|
||||
pub mod bus {
|
||||
|
||||
use super::super::super::xdg::NOTIFICATION_DEFAULT_BUS;
|
||||
|
||||
fn skip_first_slash(s: &str) -> &str {
|
||||
if let Some('/') = s.chars().next() {
|
||||
&s[1..]
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
type BusNameType = dbus::strings::BusName<'static>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NotificationBus(BusNameType);
|
||||
|
||||
impl Default for NotificationBus {
|
||||
fn default() -> Self {
|
||||
Self(dbus::strings::BusName::from_slice(NOTIFICATION_DEFAULT_BUS).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl NotificationBus {
|
||||
fn namespaced_custom(custom_path: &str) -> Option<String> {
|
||||
// abusing path for semantic join
|
||||
skip_first_slash(
|
||||
PathBuf::from("/de/hoodie/Notification")
|
||||
.join(custom_path)
|
||||
.to_str()?,
|
||||
)
|
||||
.replace('/', ".")
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn custom(custom_path: &str) -> Option<Self> {
|
||||
let name = dbus::strings::BusName::new(Self::namespaced_custom(custom_path)?).ok()?;
|
||||
Some(Self(name))
|
||||
}
|
||||
|
||||
pub fn into_name(self) -> BusNameType {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to a shown notification.
|
||||
///
|
||||
/// This keeps a connection alive to ensure actions work on certain desktops.
|
||||
#[derive(Debug)]
|
||||
pub struct DbusNotificationHandle {
|
||||
pub(crate) id: u32,
|
||||
pub(crate) connection: Connection,
|
||||
pub(crate) notification: Notification,
|
||||
}
|
||||
|
||||
impl DbusNotificationHandle {
|
||||
pub(crate) fn new(
|
||||
id: u32,
|
||||
connection: Connection,
|
||||
notification: Notification,
|
||||
) -> DbusNotificationHandle {
|
||||
DbusNotificationHandle {
|
||||
id,
|
||||
connection,
|
||||
notification,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait_for_action(self, invocation_closure: impl ActionResponseHandler) {
|
||||
wait_for_action_signal(&self.connection, self.id, invocation_closure);
|
||||
}
|
||||
|
||||
pub fn close(self) {
|
||||
let mut message = build_message("CloseNotification", Default::default());
|
||||
message.append_items(&[self.id.into()]);
|
||||
let _ = self.connection.send(message); // If closing fails there's nothing we could do anyway
|
||||
}
|
||||
|
||||
pub fn on_close<F>(self, closure: F)
|
||||
where
|
||||
F: FnOnce(CloseReason),
|
||||
{
|
||||
self.wait_for_action(|action: &ActionResponse| {
|
||||
if let ActionResponse::Closed(reason) = action {
|
||||
closure(*reason);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
self.id = send_notification_via_connection(&self.notification, self.id, &self.connection)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_notification_via_connection(
|
||||
notification: &Notification,
|
||||
id: u32,
|
||||
connection: &Connection,
|
||||
) -> Result<u32> {
|
||||
send_notification_via_connection_at_bus(notification, id, connection, Default::default())
|
||||
}
|
||||
|
||||
pub fn send_notification_via_connection_at_bus(
|
||||
notification: &Notification,
|
||||
id: u32,
|
||||
connection: &Connection,
|
||||
bus: NotificationBus,
|
||||
) -> Result<u32> {
|
||||
let mut message = build_message("Notify", bus);
|
||||
let timeout: i32 = notification.timeout.into();
|
||||
message.append_items(&[
|
||||
notification.appname.to_owned().into(), // appname
|
||||
id.into(), // notification to update
|
||||
notification.icon.to_owned().into(), // icon
|
||||
notification.summary.to_owned().into(), // summary (title)
|
||||
notification.body.to_owned().into(), // body
|
||||
pack_actions(notification), // actions
|
||||
pack_hints(notification)?, // hints
|
||||
timeout.into(), // timeout
|
||||
]);
|
||||
|
||||
let reply = connection.send_with_reply_and_block(message, 2000)?;
|
||||
|
||||
match reply.get_items().first() {
|
||||
Some(MessageItem::UInt32(ref id)) => Ok(*id),
|
||||
_ => Ok(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect_and_send_notification(
|
||||
notification: &Notification,
|
||||
) -> Result<DbusNotificationHandle> {
|
||||
let bus = notification.bus.clone();
|
||||
connect_and_send_notification_at_bus(notification, bus)
|
||||
}
|
||||
|
||||
pub fn connect_and_send_notification_at_bus(
|
||||
notification: &Notification,
|
||||
bus: NotificationBus,
|
||||
) -> Result<DbusNotificationHandle> {
|
||||
let connection = Connection::get_private(BusType::Session)?;
|
||||
let inner_id = notification.id.unwrap_or(0);
|
||||
let id = send_notification_via_connection_at_bus(notification, inner_id, &connection, bus)?;
|
||||
|
||||
Ok(DbusNotificationHandle::new(
|
||||
id,
|
||||
connection,
|
||||
notification.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
fn build_message(method_name: &str, bus: NotificationBus) -> Message {
|
||||
Message::new_method_call(
|
||||
bus.into_name(),
|
||||
NOTIFICATION_OBJECTPATH,
|
||||
NOTIFICATION_INTERFACE,
|
||||
method_name,
|
||||
)
|
||||
.unwrap_or_else(|_| panic!("Error building message call {:?}.", method_name))
|
||||
}
|
||||
|
||||
pub fn pack_hints(notification: &Notification) -> Result<MessageItem> {
|
||||
if !notification.hints.is_empty() || !notification.hints_unique.is_empty() {
|
||||
let hints = notification
|
||||
.get_hints()
|
||||
.cloned()
|
||||
.map(HintMessage::wrap_hint)
|
||||
.collect::<Vec<(MessageItem, MessageItem)>>();
|
||||
|
||||
if let Ok(array) = MessageItem::new_dict(hints) {
|
||||
return Ok(array);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(MessageItem::Array(
|
||||
MessageItemArray::new(vec![], "a{sv}".into()).unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn pack_actions(notification: &Notification) -> MessageItem {
|
||||
if !notification.actions.is_empty() {
|
||||
let mut actions = vec![];
|
||||
for action in ¬ification.actions {
|
||||
actions.push(action.to_owned().into());
|
||||
}
|
||||
if let Ok(array) = MessageItem::new_array(actions) {
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
MessageItem::Array(MessageItemArray::new(vec![], "as".into()).unwrap())
|
||||
}
|
||||
|
||||
pub fn get_capabilities() -> Result<Vec<String>> {
|
||||
let mut capabilities = vec![];
|
||||
|
||||
let message = build_message("GetCapabilities", Default::default());
|
||||
let connection = Connection::get_private(BusType::Session)?;
|
||||
let reply = connection.send_with_reply_and_block(message, 2000)?;
|
||||
|
||||
if let Some(MessageItem::Array(items)) = reply.get_items().first() {
|
||||
for item in items.iter() {
|
||||
if let MessageItem::Str(ref cap) = *item {
|
||||
capabilities.push(cap.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(capabilities)
|
||||
}
|
||||
|
||||
fn unwrap_message_string(item: Option<&MessageItem>) -> String {
|
||||
match item {
|
||||
Some(MessageItem::Str(value)) => value.to_owned(),
|
||||
_ => "".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::get_first)]
|
||||
pub fn get_server_information() -> Result<ServerInformation> {
|
||||
let message = build_message("GetServerInformation", Default::default());
|
||||
let connection = Connection::get_private(BusType::Session)?;
|
||||
let reply = connection.send_with_reply_and_block(message, 2000)?;
|
||||
|
||||
let items = reply.get_items();
|
||||
|
||||
Ok(ServerInformation {
|
||||
name: unwrap_message_string(items.get(0)),
|
||||
vendor: unwrap_message_string(items.get(1)),
|
||||
version: unwrap_message_string(items.get(2)),
|
||||
spec_version: unwrap_message_string(items.get(3)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Listens for the `ActionInvoked(UInt32, String)` Signal.
|
||||
///
|
||||
/// No need to use this, check out `Notification::show_and_wait_for_action(FnOnce(action:&str))`
|
||||
pub fn handle_action(id: u32, func: impl ActionResponseHandler) {
|
||||
let connection = Connection::get_private(BusType::Session).unwrap();
|
||||
wait_for_action_signal(&connection, id, func);
|
||||
}
|
||||
|
||||
// Listens for the `ActionInvoked(UInt32, String)` signal.
|
||||
fn wait_for_action_signal(connection: &Connection, id: u32, handler: impl ActionResponseHandler) {
|
||||
connection
|
||||
.add_match(&format!(
|
||||
"interface='{}',member='ActionInvoked'",
|
||||
NOTIFICATION_INTERFACE
|
||||
))
|
||||
.unwrap();
|
||||
connection
|
||||
.add_match(&format!(
|
||||
"interface='{}',member='NotificationClosed'",
|
||||
NOTIFICATION_INTERFACE
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
for item in connection.iter(1000) {
|
||||
if let ConnectionItem::Signal(message) = item {
|
||||
let items = message.get_items();
|
||||
|
||||
let (path, interface, member) = (
|
||||
message.path().map_or_else(String::new, |p| {
|
||||
p.into_cstring().to_string_lossy().into_owned()
|
||||
}),
|
||||
message.interface().map_or_else(String::new, |p| {
|
||||
p.into_cstring().to_string_lossy().into_owned()
|
||||
}),
|
||||
message.member().map_or_else(String::new, |p| {
|
||||
p.into_cstring().to_string_lossy().into_owned()
|
||||
}),
|
||||
);
|
||||
match (path.as_str(), interface.as_str(), member.as_str()) {
|
||||
// match (protocol.unwrap(), iface.unwrap(), member.unwrap()) {
|
||||
// Action Invoked
|
||||
(path, interface, "ActionInvoked")
|
||||
if path == NOTIFICATION_OBJECTPATH && interface == NOTIFICATION_INTERFACE =>
|
||||
{
|
||||
if let (&MessageItem::UInt32(nid), MessageItem::Str(ref action)) =
|
||||
(&items[0], &items[1])
|
||||
{
|
||||
if nid == id {
|
||||
handler.call(&ActionResponse::Custom(action));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notification Closed
|
||||
(path, interface, "NotificationClosed")
|
||||
if path == NOTIFICATION_OBJECTPATH && interface == NOTIFICATION_INTERFACE =>
|
||||
{
|
||||
if let (&MessageItem::UInt32(nid), &MessageItem::UInt32(reason)) =
|
||||
(&items[0], &items[1])
|
||||
{
|
||||
if nid == id {
|
||||
handler.call(&ActionResponse::Closed(reason.into()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
(..) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,544 +0,0 @@
|
||||
//! This module contains `XDG` and `DBus` specific code.
|
||||
//!
|
||||
//! it should not be available under any platform other than `(unix, not(target_os = "macos"))`
|
||||
|
||||
#[cfg(feature = "dbus")]
|
||||
use dbus::ffidisp::Connection as DbusConnection;
|
||||
#[cfg(feature = "zbus")]
|
||||
use zbus::{block_on, zvariant};
|
||||
|
||||
use super::{error::*, notification::Notification};
|
||||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
#[cfg(feature = "dbus")]
|
||||
mod dbus_rs;
|
||||
#[cfg(all(feature = "dbus", not(feature = "zbus")))]
|
||||
use dbus_rs::bus;
|
||||
|
||||
#[cfg(feature = "zbus")]
|
||||
mod zbus_rs;
|
||||
#[cfg(all(feature = "zbus", not(feature = "dbus")))]
|
||||
use zbus_rs::bus;
|
||||
|
||||
#[cfg(all(feature = "dbus", feature = "zbus"))]
|
||||
mod bus;
|
||||
|
||||
// #[cfg(all(feature = "server", feature = "dbus", unix, not(target_os = "macos")))]
|
||||
// pub mod server_dbus;
|
||||
|
||||
// #[cfg(all(feature = "server", feature = "zbus", unix, not(target_os = "macos")))]
|
||||
// pub mod server_zbus;
|
||||
|
||||
// #[cfg(all(feature = "server", unix, not(target_os = "macos")))]
|
||||
// pub mod server;
|
||||
|
||||
#[cfg(not(feature = "debug_namespace"))]
|
||||
#[doc(hidden)]
|
||||
pub static NOTIFICATION_DEFAULT_BUS: &str = "org.freedesktop.Notifications";
|
||||
|
||||
#[cfg(feature = "debug_namespace")]
|
||||
#[doc(hidden)]
|
||||
// #[deprecated]
|
||||
pub static NOTIFICATION_DEFAULT_BUS: &str = "de.hoodie.Notifications";
|
||||
|
||||
#[doc(hidden)]
|
||||
pub static NOTIFICATION_INTERFACE: &str = "org.freedesktop.Notifications";
|
||||
|
||||
#[doc(hidden)]
|
||||
pub static NOTIFICATION_OBJECTPATH: &str = "/org/freedesktop/Notifications";
|
||||
|
||||
pub(crate) use bus::NotificationBus;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum NotificationHandleInner {
|
||||
#[cfg(feature = "dbus")]
|
||||
Dbus(dbus_rs::DbusNotificationHandle),
|
||||
|
||||
#[cfg(feature = "zbus")]
|
||||
Zbus(zbus_rs::ZbusNotificationHandle),
|
||||
}
|
||||
|
||||
/// A handle to a shown notification.
|
||||
///
|
||||
/// This keeps a connection alive to ensure actions work on certain desktops.
|
||||
#[derive(Debug)]
|
||||
pub struct NotificationHandle {
|
||||
inner: NotificationHandleInner,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl NotificationHandle {
|
||||
#[cfg(feature = "dbus")]
|
||||
pub(crate) fn for_dbus(
|
||||
id: u32,
|
||||
connection: DbusConnection,
|
||||
notification: Notification,
|
||||
) -> NotificationHandle {
|
||||
NotificationHandle {
|
||||
inner: dbus_rs::DbusNotificationHandle::new(id, connection, notification).into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zbus")]
|
||||
pub(crate) fn for_zbus(
|
||||
id: u32,
|
||||
connection: zbus::Connection,
|
||||
notification: Notification,
|
||||
) -> NotificationHandle {
|
||||
NotificationHandle {
|
||||
inner: zbus_rs::ZbusNotificationHandle::new(id, connection, notification).into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits for the user to act on a notification and then calls
|
||||
/// `invocation_closure` with the name of the corresponding action.
|
||||
pub fn wait_for_action<F>(self, invocation_closure: F)
|
||||
where
|
||||
F: FnOnce(&str),
|
||||
{
|
||||
match self.inner {
|
||||
#[cfg(feature = "dbus")]
|
||||
NotificationHandleInner::Dbus(inner) => {
|
||||
inner.wait_for_action(|action: &ActionResponse| match action {
|
||||
ActionResponse::Custom(action) => invocation_closure(action),
|
||||
ActionResponse::Closed(_reason) => invocation_closure("__closed"), // FIXME: remove backward compatibility with 5.0
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "zbus")]
|
||||
NotificationHandleInner::Zbus(inner) => {
|
||||
block_on(
|
||||
inner.wait_for_action(|action: &ActionResponse| match action {
|
||||
ActionResponse::Custom(action) => invocation_closure(action),
|
||||
ActionResponse::Closed(_reason) => invocation_closure("__closed"), // FIXME: remove backward compatibility with 5.0
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Manually close the notification
|
||||
pub fn close(self) {
|
||||
match self.inner {
|
||||
#[cfg(feature = "dbus")]
|
||||
NotificationHandleInner::Dbus(inner) => inner.close(),
|
||||
#[cfg(feature = "zbus")]
|
||||
NotificationHandleInner::Zbus(inner) => block_on(inner.close()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes a closure after the notification has closed.
|
||||
pub fn on_close<A>(self, handler: impl CloseHandler<A>) {
|
||||
match self.inner {
|
||||
#[cfg(feature = "dbus")]
|
||||
NotificationHandleInner::Dbus(inner) => {
|
||||
inner.wait_for_action(|action: &ActionResponse| {
|
||||
if let ActionResponse::Closed(reason) = action {
|
||||
handler.call(*reason);
|
||||
}
|
||||
});
|
||||
}
|
||||
#[cfg(feature = "zbus")]
|
||||
NotificationHandleInner::Zbus(inner) => {
|
||||
block_on(inner.wait_for_action(|action: &ActionResponse| {
|
||||
if let ActionResponse::Closed(reason) = action {
|
||||
handler.call(*reason);
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
match self.inner {
|
||||
#[cfg(feature = "dbus")]
|
||||
NotificationHandleInner::Dbus(ref mut inner) => inner.update(),
|
||||
#[cfg(feature = "zbus")]
|
||||
NotificationHandleInner::Zbus(ref mut inner) => inner.update(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the Handle's id.
|
||||
pub fn id(&self) -> u32 {
|
||||
match self.inner {
|
||||
#[cfg(feature = "dbus")]
|
||||
NotificationHandleInner::Dbus(ref inner) => inner.id,
|
||||
#[cfg(feature = "zbus")]
|
||||
NotificationHandleInner::Zbus(ref inner) => inner.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Required for `DerefMut`
|
||||
impl Deref for NotificationHandle {
|
||||
type Target = Notification;
|
||||
|
||||
fn deref(&self) -> &Notification {
|
||||
match self.inner {
|
||||
#[cfg(feature = "dbus")]
|
||||
NotificationHandleInner::Dbus(ref inner) => &inner.notification,
|
||||
#[cfg(feature = "zbus")]
|
||||
NotificationHandleInner::Zbus(ref inner) => &inner.notification,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow you to easily modify notification properties
|
||||
impl DerefMut for NotificationHandle {
|
||||
fn deref_mut(&mut self) -> &mut Notification {
|
||||
match self.inner {
|
||||
#[cfg(feature = "dbus")]
|
||||
NotificationHandleInner::Dbus(ref mut inner) => &mut inner.notification,
|
||||
#[cfg(feature = "zbus")]
|
||||
NotificationHandleInner::Zbus(ref mut inner) => &mut inner.notification,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dbus")]
|
||||
impl From<dbus_rs::DbusNotificationHandle> for NotificationHandleInner {
|
||||
fn from(handle: dbus_rs::DbusNotificationHandle) -> NotificationHandleInner {
|
||||
NotificationHandleInner::Dbus(handle)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zbus")]
|
||||
impl From<zbus_rs::ZbusNotificationHandle> for NotificationHandleInner {
|
||||
fn from(handle: zbus_rs::ZbusNotificationHandle) -> NotificationHandleInner {
|
||||
NotificationHandleInner::Zbus(handle)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dbus")]
|
||||
impl From<dbus_rs::DbusNotificationHandle> for NotificationHandle {
|
||||
fn from(handle: dbus_rs::DbusNotificationHandle) -> NotificationHandle {
|
||||
NotificationHandle {
|
||||
inner: handle.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zbus")]
|
||||
impl From<zbus_rs::ZbusNotificationHandle> for NotificationHandle {
|
||||
fn from(handle: zbus_rs::ZbusNotificationHandle) -> NotificationHandle {
|
||||
NotificationHandle {
|
||||
inner: handle.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// here be public functions
|
||||
|
||||
// TODO: breaking change, wait for 5.0
|
||||
// #[cfg(all(feature = "dbus", feature = "zbus"))]
|
||||
//compile_error!("the z and d features are mutually exclusive");
|
||||
|
||||
#[cfg(all(
|
||||
not(any(feature = "dbus", feature = "zbus")),
|
||||
unix,
|
||||
not(target_os = "macos")
|
||||
))]
|
||||
compile_error!("you have to build with either zbus or dbus turned on");
|
||||
|
||||
/// Which Dbus implementation are we using?
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum DbusStack {
|
||||
/// using [dbus-rs](https://docs.rs/dbus-rs)
|
||||
Dbus,
|
||||
/// using [zbus](https://docs.rs/zbus)
|
||||
Zbus,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "dbus", feature = "zbus"))]
|
||||
const DBUS_SWITCH_VAR: &str = "DBUSRS";
|
||||
|
||||
#[cfg(all(feature = "zbus", not(feature = "dbus")))]
|
||||
pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
|
||||
block_on(zbus_rs::connect_and_send_notification(notification)).map(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "async", feature = "zbus"))]
|
||||
pub(crate) async fn show_notification_async(
|
||||
notification: &Notification,
|
||||
) -> Result<NotificationHandle> {
|
||||
zbus_rs::connect_and_send_notification(notification)
|
||||
.await
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "async", feature = "zbus"))]
|
||||
pub(crate) async fn show_notification_async_at_bus(
|
||||
notification: &Notification,
|
||||
bus: NotificationBus,
|
||||
) -> Result<NotificationHandle> {
|
||||
zbus_rs::connect_and_send_notification_at_bus(notification, bus)
|
||||
.await
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "dbus", not(feature = "zbus")))]
|
||||
pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
|
||||
dbus_rs::connect_and_send_notification(notification).map(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "dbus", feature = "zbus"))]
|
||||
pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
|
||||
if std::env::var(DBUS_SWITCH_VAR).is_ok() {
|
||||
dbus_rs::connect_and_send_notification(notification).map(Into::into)
|
||||
} else {
|
||||
block_on(zbus_rs::connect_and_send_notification(notification)).map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the currently used [`DbusStack`]
|
||||
///
|
||||
/// (zbus only)
|
||||
#[cfg(all(feature = "zbus", not(feature = "dbus")))]
|
||||
pub fn dbus_stack() -> Option<DbusStack> {
|
||||
Some(DbusStack::Zbus)
|
||||
}
|
||||
|
||||
/// Get the currently used [`DbusStack`]
|
||||
///
|
||||
/// (dbus-rs only)
|
||||
#[cfg(all(feature = "dbus", not(feature = "zbus")))]
|
||||
pub fn dbus_stack() -> Option<DbusStack> {
|
||||
Some(DbusStack::Dbus)
|
||||
}
|
||||
|
||||
/// Get the currently used [`DbusStack`]
|
||||
///
|
||||
/// both dbus-rs and zbus, switch via `$ZBUS_NOTIFICATION`
|
||||
#[cfg(all(feature = "dbus", feature = "zbus"))]
|
||||
pub fn dbus_stack() -> Option<DbusStack> {
|
||||
Some(if std::env::var(DBUS_SWITCH_VAR).is_ok() {
|
||||
DbusStack::Dbus
|
||||
} else {
|
||||
DbusStack::Zbus
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the currently used [`DbusStack`]
|
||||
///
|
||||
/// neither zbus nor dbus-rs are configured
|
||||
#[cfg(all(not(feature = "dbus"), not(feature = "zbus")))]
|
||||
pub fn dbus_stack() -> Option<DbusStack> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Get list of all capabilities of the running notification server.
|
||||
///
|
||||
/// (zbus only)
|
||||
#[cfg(all(feature = "zbus", not(feature = "dbus")))]
|
||||
pub fn get_capabilities() -> Result<Vec<String>> {
|
||||
block_on(zbus_rs::get_capabilities())
|
||||
}
|
||||
|
||||
/// Get list of all capabilities of the running notification server.
|
||||
///
|
||||
/// (dbus-rs only)
|
||||
#[cfg(all(feature = "dbus", not(feature = "zbus")))]
|
||||
pub fn get_capabilities() -> Result<Vec<String>> {
|
||||
dbus_rs::get_capabilities()
|
||||
}
|
||||
|
||||
/// Get list of all capabilities of the running notification server.
|
||||
///
|
||||
/// both dbus-rs and zbus, switch via `$ZBUS_NOTIFICATION`
|
||||
#[cfg(all(feature = "dbus", feature = "zbus"))]
|
||||
pub fn get_capabilities() -> Result<Vec<String>> {
|
||||
if std::env::var(DBUS_SWITCH_VAR).is_ok() {
|
||||
dbus_rs::get_capabilities()
|
||||
} else {
|
||||
block_on(zbus_rs::get_capabilities())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a struct containing `ServerInformation`.
|
||||
///
|
||||
/// This struct contains `name`, `vendor`, `version` and `spec_version` of the notification server
|
||||
/// running.
|
||||
///
|
||||
/// (zbus only)
|
||||
#[cfg(all(feature = "zbus", not(feature = "dbus")))]
|
||||
pub fn get_server_information() -> Result<ServerInformation> {
|
||||
block_on(zbus_rs::get_server_information())
|
||||
}
|
||||
|
||||
/// Returns a struct containing `ServerInformation`.
|
||||
///
|
||||
/// This struct contains `name`, `vendor`, `version` and `spec_version` of the notification server
|
||||
/// running.
|
||||
///
|
||||
/// (dbus-rs only)
|
||||
#[cfg(all(feature = "dbus", not(feature = "zbus")))]
|
||||
pub fn get_server_information() -> Result<ServerInformation> {
|
||||
dbus_rs::get_server_information()
|
||||
}
|
||||
|
||||
/// Returns a struct containing `ServerInformation`.
|
||||
///
|
||||
/// This struct contains `name`, `vendor`, `version` and `spec_version` of the notification server
|
||||
/// running.
|
||||
///
|
||||
/// both dbus-rs and zbus, switch via `$ZBUS_NOTIFICATION`
|
||||
#[cfg(all(feature = "dbus", feature = "zbus"))]
|
||||
pub fn get_server_information() -> Result<ServerInformation> {
|
||||
if std::env::var(DBUS_SWITCH_VAR).is_ok() {
|
||||
dbus_rs::get_server_information()
|
||||
} else {
|
||||
block_on(zbus_rs::get_server_information())
|
||||
}
|
||||
}
|
||||
|
||||
/// Return value of `get_server_information()`.
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "zbus", derive(zvariant::Type))]
|
||||
pub struct ServerInformation {
|
||||
/// The product name of the server.
|
||||
pub name: String,
|
||||
/// The vendor name.
|
||||
pub vendor: String,
|
||||
/// The server's version string.
|
||||
pub version: String,
|
||||
/// The specification version the server is compliant with.
|
||||
pub spec_version: String,
|
||||
}
|
||||
|
||||
/// Strictly internal.
|
||||
/// The NotificationServer implemented here exposes a "Stop" function.
|
||||
/// stops the notification server
|
||||
#[cfg(all(feature = "server", unix, not(target_os = "macos")))]
|
||||
#[doc(hidden)]
|
||||
pub fn stop_server() {
|
||||
#[cfg(feature = "dbus")]
|
||||
dbus_rs::stop_server()
|
||||
}
|
||||
|
||||
/// Listens for the `ActionInvoked(UInt32, String)` Signal.
|
||||
///
|
||||
/// No need to use this, check out [`NotificationHandle::wait_for_action`]
|
||||
/// (xdg only)
|
||||
#[cfg(all(feature = "zbus", not(feature = "dbus")))]
|
||||
// #[deprecated(note="please use [`NotificationHandle::wait_for_action`]")]
|
||||
pub fn handle_action<F>(id: u32, func: F)
|
||||
where
|
||||
F: FnOnce(&ActionResponse),
|
||||
{
|
||||
block_on(zbus_rs::handle_action(id, func));
|
||||
}
|
||||
|
||||
/// Listens for the `ActionInvoked(UInt32, String)` Signal.
|
||||
///
|
||||
/// No need to use this, check out [`NotificationHandle::wait_for_action`]
|
||||
/// (xdg only)
|
||||
#[cfg(all(feature = "dbus", not(feature = "zbus")))]
|
||||
// #[deprecated(note="please use `NotificationHandle::wait_for_action`")]
|
||||
pub fn handle_action<F>(id: u32, func: F)
|
||||
where
|
||||
F: FnOnce(&ActionResponse),
|
||||
{
|
||||
dbus_rs::handle_action(id, func);
|
||||
}
|
||||
|
||||
/// Listens for the `ActionInvoked(UInt32, String)` Signal.
|
||||
///
|
||||
/// No need to use this, check out [`NotificationHandle::wait_for_action`]
|
||||
/// both dbus-rs and zbus, switch via `$ZBUS_NOTIFICATION`
|
||||
#[cfg(all(feature = "dbus", feature = "zbus"))]
|
||||
// #[deprecated(note="please use `NotificationHandle::wait_for_action`")]
|
||||
pub fn handle_action<F>(id: u32, func: F)
|
||||
where
|
||||
F: FnOnce(&ActionResponse),
|
||||
{
|
||||
if std::env::var(DBUS_SWITCH_VAR).is_ok() {
|
||||
dbus_rs::handle_action(id, func);
|
||||
} else {
|
||||
block_on(zbus_rs::handle_action(id, func));
|
||||
}
|
||||
}
|
||||
|
||||
/// Reason passed to `NotificationClosed` Signal
|
||||
///
|
||||
/// ## Specification
|
||||
/// As listed under [Table 8. `NotificationClosed` Parameters](https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html#idm46350804042704)
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum CloseReason {
|
||||
/// The notification expired
|
||||
Expired,
|
||||
/// The notification was dismissed by the user
|
||||
Dismissed,
|
||||
/// The notification was closed by a call to `CloseNotification`
|
||||
CloseAction,
|
||||
/// Undefined/Reserved reason
|
||||
Other(u32),
|
||||
}
|
||||
|
||||
impl From<u32> for CloseReason {
|
||||
fn from(raw_reason: u32) -> Self {
|
||||
match raw_reason {
|
||||
1 => CloseReason::Expired,
|
||||
2 => CloseReason::Dismissed,
|
||||
3 => CloseReason::CloseAction,
|
||||
other => CloseReason::Other(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper Trait implemented by `Fn()`
|
||||
pub trait ActionResponseHandler {
|
||||
fn call(self, response: &ActionResponse);
|
||||
}
|
||||
|
||||
// impl<F: Send + Sync + 'static> ActionResponseHandler for F
|
||||
impl<F> ActionResponseHandler for F
|
||||
where
|
||||
F: FnOnce(&ActionResponse),
|
||||
{
|
||||
fn call(self, res: &ActionResponse) {
|
||||
(self)(res);
|
||||
}
|
||||
}
|
||||
|
||||
/// Response to an action
|
||||
pub enum ActionResponse<'a> {
|
||||
/// Custom Action configured by the Notification.
|
||||
Custom(&'a str),
|
||||
|
||||
/// The Notification was closed.
|
||||
Closed(CloseReason),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for ActionResponse<'a> {
|
||||
fn from(raw: &'a str) -> Self {
|
||||
Self::Custom(raw)
|
||||
}
|
||||
}
|
||||
|
||||
/// Your handy callback for the `Close` signal of your Notification.
|
||||
///
|
||||
/// This is implemented by `Fn()` and `Fn(CloseReason)`, so there is probably no good reason for you to manually implement this trait.
|
||||
/// Should you find one anyway, please notify me and I'll gladly remove this obviously redundant comment.
|
||||
pub trait CloseHandler<T> {
|
||||
/// This is called with the [`CloseReason`].
|
||||
fn call(&self, reason: CloseReason);
|
||||
}
|
||||
|
||||
impl<F> CloseHandler<CloseReason> for F
|
||||
where
|
||||
F: Fn(CloseReason),
|
||||
{
|
||||
fn call(&self, reason: CloseReason) {
|
||||
self(reason);
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> CloseHandler<()> for F
|
||||
where
|
||||
F: Fn(),
|
||||
{
|
||||
fn call(&self, _: CloseReason) {
|
||||
self();
|
||||
}
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
use super::super::{error::*, notification::Notification, xdg};
|
||||
use zbus::{export::futures_util::TryStreamExt, MatchRule};
|
||||
|
||||
use super::{bus::NotificationBus, ActionResponse, ActionResponseHandler, CloseReason};
|
||||
|
||||
pub mod bus {
|
||||
|
||||
use super::super::super::xdg::NOTIFICATION_DEFAULT_BUS;
|
||||
|
||||
fn skip_first_slash(s: &str) -> &str {
|
||||
if let Some('/') = s.chars().next() {
|
||||
&s[1..]
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
type BusNameType = zbus::names::WellKnownName<'static>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NotificationBus(BusNameType);
|
||||
|
||||
impl Default for NotificationBus {
|
||||
#[cfg(feature = "zbus")]
|
||||
fn default() -> Self {
|
||||
Self(zbus::names::WellKnownName::from_static_str(NOTIFICATION_DEFAULT_BUS).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl NotificationBus {
|
||||
fn namespaced_custom(custom_path: &str) -> Option<String> {
|
||||
// abusing path for semantic join
|
||||
skip_first_slash(
|
||||
PathBuf::from("/de/hoodie/Notification")
|
||||
.join(custom_path)
|
||||
.to_str()?,
|
||||
)
|
||||
.replace('/', ".")
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn custom(custom_path: &str) -> Option<Self> {
|
||||
let name =
|
||||
zbus::names::WellKnownName::try_from(Self::namespaced_custom(custom_path)?).ok()?;
|
||||
Some(Self(name))
|
||||
}
|
||||
|
||||
pub fn into_name(self) -> BusNameType {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to a shown notification.
|
||||
///
|
||||
/// This keeps a connection alive to ensure actions work on certain desktops.
|
||||
#[derive(Debug)]
|
||||
pub struct ZbusNotificationHandle {
|
||||
pub(crate) id: u32,
|
||||
pub(crate) connection: zbus::Connection,
|
||||
pub(crate) notification: Notification,
|
||||
}
|
||||
|
||||
impl ZbusNotificationHandle {
|
||||
pub(crate) fn new(
|
||||
id: u32,
|
||||
connection: zbus::Connection,
|
||||
notification: Notification,
|
||||
) -> ZbusNotificationHandle {
|
||||
ZbusNotificationHandle {
|
||||
id,
|
||||
connection,
|
||||
notification,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn wait_for_action(self, invocation_closure: impl ActionResponseHandler) {
|
||||
wait_for_action_signal(&self.connection, self.id, invocation_closure).await;
|
||||
}
|
||||
|
||||
pub async fn close_fallible(self) -> Result<()> {
|
||||
self.connection
|
||||
.call_method(
|
||||
Some(self.notification.bus.clone().into_name()),
|
||||
xdg::NOTIFICATION_OBJECTPATH,
|
||||
Some(xdg::NOTIFICATION_INTERFACE),
|
||||
"CloseNotification",
|
||||
&(self.id),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn close(self) {
|
||||
self.close_fallible().await.unwrap();
|
||||
}
|
||||
|
||||
pub fn on_close<F>(self, closure: F)
|
||||
where
|
||||
F: FnOnce(CloseReason),
|
||||
{
|
||||
zbus::block_on(self.wait_for_action(|action: &ActionResponse| {
|
||||
if let ActionResponse::Closed(reason) = action {
|
||||
closure(*reason);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn update_fallible(&mut self) -> Result<()> {
|
||||
self.id = zbus::block_on(send_notification_via_connection(
|
||||
&self.notification,
|
||||
self.id,
|
||||
&self.connection,
|
||||
))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
self.update_fallible().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_notification_via_connection(
|
||||
notification: &Notification,
|
||||
id: u32,
|
||||
connection: &zbus::Connection,
|
||||
) -> Result<u32> {
|
||||
send_notification_via_connection_at_bus(notification, id, connection, Default::default()).await
|
||||
}
|
||||
|
||||
async fn send_notification_via_connection_at_bus(
|
||||
notification: &Notification,
|
||||
id: u32,
|
||||
connection: &zbus::Connection,
|
||||
bus: NotificationBus,
|
||||
) -> Result<u32> {
|
||||
let reply: u32 = connection
|
||||
.call_method(
|
||||
Some(bus.into_name()),
|
||||
xdg::NOTIFICATION_OBJECTPATH,
|
||||
Some(xdg::NOTIFICATION_INTERFACE),
|
||||
"Notify",
|
||||
&(
|
||||
¬ification.appname,
|
||||
id,
|
||||
¬ification.icon,
|
||||
¬ification.summary,
|
||||
¬ification.body,
|
||||
¬ification.actions,
|
||||
super::super::hints::hints_to_map(notification),
|
||||
i32::from(notification.timeout),
|
||||
),
|
||||
)
|
||||
.await?
|
||||
.body()
|
||||
.deserialize()?;
|
||||
Ok(reply)
|
||||
}
|
||||
|
||||
pub async fn connect_and_send_notification(
|
||||
notification: &Notification,
|
||||
) -> Result<ZbusNotificationHandle> {
|
||||
let bus = notification.bus.clone();
|
||||
connect_and_send_notification_at_bus(notification, bus).await
|
||||
}
|
||||
|
||||
pub(crate) async fn connect_and_send_notification_at_bus(
|
||||
notification: &Notification,
|
||||
bus: NotificationBus,
|
||||
) -> Result<ZbusNotificationHandle> {
|
||||
let connection = zbus::Connection::session().await?;
|
||||
let inner_id = notification.id.unwrap_or(0);
|
||||
let id =
|
||||
send_notification_via_connection_at_bus(notification, inner_id, &connection, bus).await?;
|
||||
|
||||
Ok(ZbusNotificationHandle::new(
|
||||
id,
|
||||
connection,
|
||||
notification.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_capabilities_at_bus(bus: NotificationBus) -> Result<Vec<String>> {
|
||||
let connection = zbus::Connection::session().await?;
|
||||
let info: Vec<String> = connection
|
||||
.call_method(
|
||||
Some(bus.into_name()),
|
||||
xdg::NOTIFICATION_OBJECTPATH,
|
||||
Some(xdg::NOTIFICATION_INTERFACE),
|
||||
"GetCapabilities",
|
||||
&(),
|
||||
)
|
||||
.await?
|
||||
.body()
|
||||
.deserialize()?;
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
pub async fn get_capabilities() -> Result<Vec<String>> {
|
||||
get_capabilities_at_bus(Default::default()).await
|
||||
}
|
||||
|
||||
pub async fn get_server_information_at_bus(bus: NotificationBus) -> Result<xdg::ServerInformation> {
|
||||
let connection = zbus::Connection::session().await?;
|
||||
let info: xdg::ServerInformation = connection
|
||||
.call_method(
|
||||
Some(bus.into_name()),
|
||||
xdg::NOTIFICATION_OBJECTPATH,
|
||||
Some(xdg::NOTIFICATION_INTERFACE),
|
||||
"GetServerInformation",
|
||||
&(),
|
||||
)
|
||||
.await?
|
||||
.body()
|
||||
.deserialize()?;
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
pub async fn get_server_information() -> Result<xdg::ServerInformation> {
|
||||
get_server_information_at_bus(Default::default()).await
|
||||
}
|
||||
|
||||
/// Listens for the `ActionInvoked(UInt32, String)` Signal.
|
||||
///
|
||||
/// No need to use this, check out `Notification::show_and_wait_for_action(FnOnce(action:&str))`
|
||||
pub async fn handle_action(id: u32, func: impl ActionResponseHandler) {
|
||||
let connection = zbus::Connection::session().await.unwrap();
|
||||
wait_for_action_signal(&connection, id, func).await;
|
||||
}
|
||||
|
||||
async fn wait_for_action_signal(
|
||||
connection: &zbus::Connection,
|
||||
id: u32,
|
||||
handler: impl ActionResponseHandler,
|
||||
) {
|
||||
let action_signal_rule = MatchRule::builder()
|
||||
.msg_type(zbus::MessageType::Signal)
|
||||
.interface(xdg::NOTIFICATION_INTERFACE)
|
||||
.unwrap()
|
||||
.member("ActionInvoked")
|
||||
.unwrap()
|
||||
.build();
|
||||
|
||||
let proxy = zbus::fdo::DBusProxy::new(connection).await.unwrap();
|
||||
proxy.add_match_rule(action_signal_rule).await.unwrap();
|
||||
|
||||
let close_signal_rule = MatchRule::builder()
|
||||
.msg_type(zbus::MessageType::Signal)
|
||||
.interface(xdg::NOTIFICATION_INTERFACE)
|
||||
.unwrap()
|
||||
.member("NotificationClosed")
|
||||
.unwrap()
|
||||
.build();
|
||||
proxy.add_match_rule(close_signal_rule).await.unwrap();
|
||||
|
||||
while let Ok(Some(msg)) = zbus::MessageStream::from(connection).try_next().await {
|
||||
let header = msg.header();
|
||||
if let zbus::MessageType::Signal = header.message_type() {
|
||||
match header.member() {
|
||||
Some(name) if name == "ActionInvoked" => {
|
||||
match msg.body().deserialize::<(u32, String)>() {
|
||||
Ok((nid, action)) if nid == id => {
|
||||
handler.call(&ActionResponse::Custom(&action));
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Some(name) if name == "NotificationClosed" => {
|
||||
match msg.body().deserialize::<(u32, u32)>() {
|
||||
Ok((nid, reason)) if nid == id => {
|
||||
handler.call(&ActionResponse::Closed(reason.into()));
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,7 +171,6 @@ class EventEmitter<E extends Record<string, any>> {
|
||||
): this {
|
||||
const wrapper = (arg: E[typeof eventName]): void => {
|
||||
this.removeListener(eventName, wrapper);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
listener(arg);
|
||||
};
|
||||
return this.addListener(eventName, wrapper);
|
||||
@@ -204,9 +203,8 @@ class EventEmitter<E extends Record<string, any>> {
|
||||
* @since 2.0.0
|
||||
*/
|
||||
removeAllListeners<N extends keyof E>(event?: N): this {
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
if (event) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete,security/detect-object-injection
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
delete this.eventListeners[event];
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
@@ -226,9 +224,8 @@ class EventEmitter<E extends Record<string, any>> {
|
||||
*/
|
||||
emit<N extends keyof E>(eventName: N, arg: E[typeof eventName]): boolean {
|
||||
if (eventName in this.eventListeners) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,security/detect-object-injection
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
const listeners = this.eventListeners[eventName];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
for (const listener of listeners) listener(arg);
|
||||
return true;
|
||||
}
|
||||
|
||||
+172
-13
@@ -12,22 +12,15 @@
|
||||
"@tauri-apps/cli": "2.0.0-beta.20"
|
||||
}
|
||||
},
|
||||
"../../../../node_modules/.pnpm/@tauri-apps+cli@2.0.0-beta.20/node_modules/@tauri-apps/cli": {
|
||||
"node_modules/@tauri-apps/cli": {
|
||||
"version": "2.0.0-beta.20",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.0.0-beta.20.tgz",
|
||||
"integrity": "sha512-707q9uIc2oNrYHd2dtMvxTrpZXVpart5EIktnRymNOpphkLlB6WUBjHD+ga45WqTU6cNGKbYvkKqTNfshNul9Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"bin": {
|
||||
"tauri": "tauri.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "2.16.1",
|
||||
"cross-env": "7.0.3",
|
||||
"cross-spawn": "7.0.3",
|
||||
"fs-extra": "11.1.1",
|
||||
"jest": "29.7.0",
|
||||
"jest-transform-toml": "1.0.0",
|
||||
"prettier": "2.8.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
@@ -48,9 +41,175 @@
|
||||
"@tauri-apps/cli-win32-x64-msvc": "2.0.0-beta.20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli": {
|
||||
"resolved": "../../../../node_modules/.pnpm/@tauri-apps+cli@2.0.0-beta.20/node_modules/@tauri-apps/cli",
|
||||
"link": true
|
||||
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||
"version": "2.0.0-beta.20",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.0-beta.20.tgz",
|
||||
"integrity": "sha512-oCJOCib7GuYkwkBXx+ekamR8NZZU+2i3MLP+DHpDxK5gS2uhCE+CBkamJkNt6y1x6xdVnwyqZOm5RvN4SRtyIA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||
"version": "2.0.0-beta.20",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.0-beta.20.tgz",
|
||||
"integrity": "sha512-lC5QSnRExedYN4Ds6ZlSvC2PxP8qfIYBJQ5ktf+PJI5gQALdNeVtd6YnTG1ODCEklfLq9WKkGwp7JdALTU5wDA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||
"version": "2.0.0-beta.20",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.0-beta.20.tgz",
|
||||
"integrity": "sha512-nZCeBMHHye5DLOJV5k2w658hnCS+LYaOZ8y/G9l3ei+g0L/HBjlSy6r4simsAT5TG8+l3oCZzLBngfTMdDS/YA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||
"version": "2.0.0-beta.20",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.0-beta.20.tgz",
|
||||
"integrity": "sha512-B79ISVLPVBgwnCchVqwTKU+vxnFYqxKomcR4rmsvxfs0NVtT5QuNzE1k4NUQnw3966yjwhYR3mnHsSJQSB4Eyw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||
"version": "2.0.0-beta.20",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.0-beta.20.tgz",
|
||||
"integrity": "sha512-ojIkv/1uZHhcrgfIN8xgn4BBeo/Xg+bnV0wer6lD78zyxkUMWeEZ+u3mae1ejCJNhhaZOxNaUQ67MvDOiGyr5Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||
"version": "2.0.0-beta.20",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.0-beta.20.tgz",
|
||||
"integrity": "sha512-xBy1FNbHKlc7T6pOmFQQPECxJaI5A9QWX7Kb9N64cNVusoOGlvc3xHYkXMS4PTr7xXOT0yiE1Ww2OwDRJ3lYsg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||
"version": "2.0.0-beta.20",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.0-beta.20.tgz",
|
||||
"integrity": "sha512-+O6zq5jmtUxA1FUAAwF2ywPysy4NRo2Y6G+ESZDkY9XosRwdt5OUjqAsYktZA3AxDMZVei8r9buwTqUwi9ny/g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||
"version": "2.0.0-beta.20",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.0.0-beta.20.tgz",
|
||||
"integrity": "sha512-RswgMbWyOQcv53CHvIuiuhAh4kKDqaGyZfWD4VlxqX/XhkoF5gsNgr0MxzrY7pmoL+89oVI+fiGVJz4nOQE5vA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||
"version": "2.0.0-beta.20",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.0-beta.20.tgz",
|
||||
"integrity": "sha512-5lgWmDVXhX3SBGbiv5SduM1yajiRnUEJClWhSdRrEEJeXdsxpCsBEhxYnUnDCEzPKxLLn5fdBv3VrVctJ03csQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||
"version": "2.0.0-beta.20",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.0-beta.20.tgz",
|
||||
"integrity": "sha512-SuSiiVQTQPSzWlsxQp/NMzWbzDS9TdVDOw7CCfgiG5wnT2GsxzrcIAVN6i7ILsVFLxrjr0bIgPldSJcdcH84Yw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.0.0-beta.7]
|
||||
|
||||
- [`4216c051`](https://github.com/tauri-apps/plugins-workspace/commit/4216c0517fd1dcb29d0162dc2fc15291472a2b00) ([#1381](https://github.com/tauri-apps/plugins-workspace/pull/1381) by [@thewh1teagle](https://github.com/tauri-apps/plugins-workspace/../../thewh1teagle)) Made `DbInstances` public for managing database instances directly from `Rust`.
|
||||
|
||||
## \[2.0.0-beta.5]
|
||||
|
||||
- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-sql"
|
||||
version = "2.0.0-beta.6"
|
||||
version = "2.0.0-beta.7"
|
||||
description = "Interface with SQL databases."
|
||||
authors = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
@@ -87,7 +87,7 @@ fn path_mapper(mut app_path: PathBuf, connection_string: &str) -> String {
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct DbInstances(Mutex<HashMap<String, Pool<Db>>>);
|
||||
pub struct DbInstances(pub Mutex<HashMap<String, Pool<Db>>>);
|
||||
|
||||
struct Migrations(Mutex<HashMap<String, MigrationList>>);
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.0.0-beta.8]
|
||||
|
||||
- [`bf29a72b`](https://github.com/tauri-apps/plugins-workspace/commit/bf29a72baaff15214a21989df23081eee84e3b8b) ([#1454](https://github.com/tauri-apps/plugins-workspace/pull/1454) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Fix regression in updater plugin failing to update using `.msi` installer.
|
||||
|
||||
## \[2.0.0-beta.5]
|
||||
|
||||
- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-updater"
|
||||
version = "2.0.0-beta.7"
|
||||
version = "2.0.0-beta.8"
|
||||
description = "In-app updates for Tauri applications."
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
@@ -34,7 +34,7 @@ tempfile = "3"
|
||||
infer = "0.15"
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies]
|
||||
zip = { version = "1", default-features = false, optional = true }
|
||||
zip = { version = "2", default-features = false, optional = true }
|
||||
windows-sys = { version = "0.52.0", features = [ "Win32_Foundation", "Win32_UI_WindowsAndMessaging" ] }
|
||||
|
||||
[target."cfg(target_os = \"linux\")".dependencies]
|
||||
|
||||
@@ -1 +1 @@
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_UPDATER__=function(e){"use strict";function t(e,t,s,n){if("a"===s&&!n)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!n:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?n:"a"===s?n.call(e):n?n.value:t.get(e)}function s(e,t,s,n,i){if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,s),s}var n,i,a,r;"function"==typeof SuppressedError&&SuppressedError;class o{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,n.set(this,(()=>{})),i.set(this,0),a.set(this,{}),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:e,id:r})=>{if(r===t(this,i,"f")){s(this,i,r+1),t(this,n,"f").call(this,e);const o=Object.keys(t(this,a,"f"));if(o.length>0){let e=r+1;for(const s of o.sort()){if(parseInt(s)!==e)break;{const i=t(this,a,"f")[s];delete t(this,a,"f")[s],t(this,n,"f").call(this,i),e+=1}}s(this,i,e)}}else t(this,a,"f")[r.toString()]=e}))}set onmessage(e){s(this,n,e)}get onmessage(){return t(this,n,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function d(e,t={},s){return window.__TAURI_INTERNALS__.invoke(e,t,s)}n=new WeakMap,i=new WeakMap,a=new WeakMap;class l{get rid(){return t(this,r,"f")}constructor(e){r.set(this,void 0),s(this,r,e)}async close(){return d("plugin:resources|close",{rid:this.rid})}}r=new WeakMap;class c extends l{constructor(e){super(e.rid),this.available=e.available,this.currentVersion=e.currentVersion,this.version=e.version,this.date=e.date,this.body=e.body}async download(e){const t=new o;e&&(t.onmessage=e);const s=await d("plugin:updater|download",{onEvent:t,rid:this.rid});this.downloadedBytes=new l(s)}async install(){if(!this.downloadedBytes)throw"Update.install called before Update.download";await d("plugin:updater|install",{updateRid:this.rid,bytesRid:this.downloadedBytes.rid}),this.downloadedBytes=void 0}async downloadAndInstall(e){const t=new o;e&&(t.onmessage=e),await d("plugin:updater|download_and_install",{onEvent:t,rid:this.rid})}async close(){await(this.downloadedBytes?.close()),await super.close()}}return e.Update=c,e.check=async function(e){return e?.headers&&(e.headers=Array.from(new Headers(e.headers).entries())),await d("plugin:updater|check",{...e}).then((e=>e.available?new c(e):null))},e}({});Object.defineProperty(window.__TAURI__,"updater",{value:__TAURI_PLUGIN_UPDATER__})}
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_UPDATER__=function(e){"use strict";function t(e,t,s,n){if("a"===s&&!n)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!n:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?n:"a"===s?n.call(e):n?n.value:t.get(e)}function s(e,t,s,n,i){if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,s),s}var n,i,r,a;"function"==typeof SuppressedError&&SuppressedError;class o{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,n.set(this,(()=>{})),i.set(this,0),r.set(this,{}),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:e,id:a})=>{if(a===t(this,i,"f")){s(this,i,a+1),t(this,n,"f").call(this,e);const o=Object.keys(t(this,r,"f"));if(o.length>0){let e=a+1;for(const s of o.sort()){if(parseInt(s)!==e)break;{const i=t(this,r,"f")[s];delete t(this,r,"f")[s],t(this,n,"f").call(this,i),e+=1}}s(this,i,e)}}else t(this,r,"f")[a.toString()]=e}))}set onmessage(e){s(this,n,e)}get onmessage(){return t(this,n,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function d(e,t={},s){return window.__TAURI_INTERNALS__.invoke(e,t,s)}n=new WeakMap,i=new WeakMap,r=new WeakMap;class l{get rid(){return t(this,a,"f")}constructor(e){a.set(this,void 0),s(this,a,e)}async close(){return d("plugin:resources|close",{rid:this.rid})}}a=new WeakMap;class c extends l{constructor(e){super(e.rid),this.available=e.available,this.currentVersion=e.currentVersion,this.version=e.version,this.date=e.date,this.body=e.body}async download(e){const t=new o;e&&(t.onmessage=e);const s=await d("plugin:updater|download",{onEvent:t,rid:this.rid});this.downloadedBytes=new l(s)}async install(){if(!this.downloadedBytes)throw new Error("Update.install called before Update.download");await d("plugin:updater|install",{updateRid:this.rid,bytesRid:this.downloadedBytes.rid}),this.downloadedBytes=void 0}async downloadAndInstall(e){const t=new o;e&&(t.onmessage=e),await d("plugin:updater|download_and_install",{onEvent:t,rid:this.rid})}async close(){await(this.downloadedBytes?.close()),await super.close()}}return e.Update=c,e.check=async function(e){return e?.headers&&(e.headers=Array.from(new Headers(e.headers).entries())),await d("plugin:updater|check",{...e}).then((e=>e.available?new c(e):null))},e}({});Object.defineProperty(window.__TAURI__,"updater",{value:__TAURI_PLUGIN_UPDATER__})}
|
||||
|
||||
@@ -72,7 +72,7 @@ class Update extends Resource {
|
||||
/** Install downloaded updater package */
|
||||
async install(): Promise<void> {
|
||||
if (!this.downloadedBytes) {
|
||||
throw "Update.install called before Update.download";
|
||||
throw new Error("Update.install called before Update.download");
|
||||
}
|
||||
|
||||
await invoke("plugin:updater|install", {
|
||||
|
||||
@@ -49,7 +49,7 @@ impl Display for WindowsUpdateInstallMode {
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::BasicUi => "basicUI",
|
||||
Self::BasicUi => "basicUi",
|
||||
Self::Quiet => "quiet",
|
||||
Self::Passive => "passive",
|
||||
}
|
||||
|
||||
@@ -81,7 +81,9 @@ impl<R: Runtime, T: Manager<R>> UpdaterExt<R> for T {
|
||||
|
||||
let args = self.env().args_os;
|
||||
if !args.is_empty() {
|
||||
builder = builder.installer_arg("/ARGS").installer_args(args);
|
||||
builder = builder
|
||||
.nsis_installer_arg("/ARGS")
|
||||
.nsis_installer_args(args);
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
|
||||
@@ -31,6 +31,8 @@ use crate::{
|
||||
Config,
|
||||
};
|
||||
|
||||
const UPDATER_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct ReleaseManifestPlatform {
|
||||
/// Download URL for the platform
|
||||
@@ -102,6 +104,7 @@ pub struct UpdaterBuilder {
|
||||
timeout: Option<Duration>,
|
||||
proxy: Option<Url>,
|
||||
installer_args: Vec<OsString>,
|
||||
nsis_installer_args: Vec<OsString>,
|
||||
on_before_exit: Option<OnBeforeExit>,
|
||||
}
|
||||
|
||||
@@ -113,6 +116,7 @@ impl UpdaterBuilder {
|
||||
.as_ref()
|
||||
.map(|w| w.installer_args.clone())
|
||||
.unwrap_or_default(),
|
||||
nsis_installer_args: Vec::new(),
|
||||
current_version,
|
||||
config,
|
||||
version_comparator: None,
|
||||
@@ -241,6 +245,7 @@ impl UpdaterBuilder {
|
||||
proxy: self.proxy,
|
||||
endpoints,
|
||||
installer_args: self.installer_args,
|
||||
nsis_installer_args: self.nsis_installer_args,
|
||||
arch,
|
||||
target,
|
||||
json_target,
|
||||
@@ -251,6 +256,26 @@ impl UpdaterBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdaterBuilder {
|
||||
pub(crate) fn nsis_installer_arg<S>(mut self, arg: S) -> Self
|
||||
where
|
||||
S: Into<OsString>,
|
||||
{
|
||||
self.nsis_installer_args.push(arg.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn nsis_installer_args<I, S>(mut self, args: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: Into<OsString>,
|
||||
{
|
||||
let args = args.into_iter().map(|a| a.into()).collect::<Vec<_>>();
|
||||
self.nsis_installer_args.extend_from_slice(&args);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Updater {
|
||||
config: Config,
|
||||
current_version: Version,
|
||||
@@ -258,8 +283,6 @@ pub struct Updater {
|
||||
timeout: Option<Duration>,
|
||||
proxy: Option<Url>,
|
||||
endpoints: Vec<Url>,
|
||||
#[allow(dead_code)]
|
||||
installer_args: Vec<OsString>,
|
||||
arch: &'static str,
|
||||
// The `{{target}}` variable we replace in the endpoint
|
||||
target: String,
|
||||
@@ -268,6 +291,10 @@ pub struct Updater {
|
||||
headers: HeaderMap,
|
||||
extract_path: PathBuf,
|
||||
on_before_exit: Option<OnBeforeExit>,
|
||||
#[allow(unused)]
|
||||
installer_args: Vec<OsString>,
|
||||
#[allow(unused)]
|
||||
nsis_installer_args: Vec<OsString>,
|
||||
}
|
||||
|
||||
impl Updater {
|
||||
@@ -312,7 +339,7 @@ impl Updater {
|
||||
.replace("{{arch}}", self.arch)
|
||||
.parse()?;
|
||||
|
||||
let mut request = ClientBuilder::new();
|
||||
let mut request = ClientBuilder::new().user_agent(UPDATER_USER_AGENT);
|
||||
if let Some(timeout) = self.timeout {
|
||||
request = request.timeout(timeout);
|
||||
}
|
||||
@@ -370,7 +397,6 @@ impl Updater {
|
||||
current_version: self.current_version.to_string(),
|
||||
target: self.target.clone(),
|
||||
extract_path: self.extract_path.clone(),
|
||||
installer_args: self.installer_args.clone(),
|
||||
version: release.version.to_string(),
|
||||
date: release.pub_date,
|
||||
download_url: release.download_url(&self.json_target)?.to_owned(),
|
||||
@@ -379,6 +405,8 @@ impl Updater {
|
||||
timeout: self.timeout,
|
||||
proxy: self.proxy.clone(),
|
||||
headers: self.headers.clone(),
|
||||
installer_args: self.installer_args.clone(),
|
||||
nsis_installer_args: self.nsis_installer_args.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
@@ -403,11 +431,6 @@ pub struct Update {
|
||||
pub date: Option<OffsetDateTime>,
|
||||
/// Target
|
||||
pub target: String,
|
||||
/// Extract path
|
||||
#[allow(unused)]
|
||||
extract_path: PathBuf,
|
||||
#[allow(unused)]
|
||||
installer_args: Vec<OsString>,
|
||||
/// Download URL announced
|
||||
pub download_url: Url,
|
||||
/// Signature announced
|
||||
@@ -418,6 +441,13 @@ pub struct Update {
|
||||
pub proxy: Option<Url>,
|
||||
/// Request headers
|
||||
pub headers: HeaderMap,
|
||||
/// Extract path
|
||||
#[allow(unused)]
|
||||
extract_path: PathBuf,
|
||||
#[allow(unused)]
|
||||
installer_args: Vec<OsString>,
|
||||
#[allow(unused)]
|
||||
nsis_installer_args: Vec<OsString>,
|
||||
}
|
||||
|
||||
impl Resource for Update {}
|
||||
@@ -442,7 +472,7 @@ impl Update {
|
||||
HeaderValue::from_str("tauri-updater").unwrap(),
|
||||
);
|
||||
|
||||
let mut request = ClientBuilder::new();
|
||||
let mut request = ClientBuilder::new().user_agent(UPDATER_USER_AGENT);
|
||||
if let Some(timeout) = self.timeout {
|
||||
request = request.timeout(timeout);
|
||||
}
|
||||
@@ -544,6 +574,7 @@ impl Update {
|
||||
/// │ └──[AppName]_[version]_x64-setup.exe # NSIS installer
|
||||
/// └── ...
|
||||
fn install_inner(&self, bytes: &[u8]) -> Result<()> {
|
||||
use std::iter::once;
|
||||
use windows_sys::{
|
||||
w,
|
||||
Win32::UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_SHOW},
|
||||
@@ -552,24 +583,39 @@ impl Update {
|
||||
let (updater_type, path, _temp) = Self::extract(bytes)?;
|
||||
|
||||
let install_mode = self.config.install_mode();
|
||||
let mut installer_args = self.installer_args();
|
||||
match updater_type {
|
||||
WindowsUpdaterType::Nsis => {
|
||||
installer_args.extend(install_mode.nsis_args().iter().map(OsStr::new));
|
||||
installer_args.push(OsStr::new("/UPDATE"));
|
||||
}
|
||||
WindowsUpdaterType::Msi => {
|
||||
installer_args.extend(install_mode.msiexec_args().iter().map(OsStr::new));
|
||||
installer_args.push(OsStr::new("/promptrestart"));
|
||||
}
|
||||
let installer_args: Vec<&OsStr> = match updater_type {
|
||||
WindowsUpdaterType::Nsis => install_mode
|
||||
.nsis_args()
|
||||
.iter()
|
||||
.map(OsStr::new)
|
||||
.chain(once(OsStr::new("/UPDATE")))
|
||||
.chain(self.nsis_installer_args())
|
||||
.chain(self.installer_args())
|
||||
.collect(),
|
||||
WindowsUpdaterType::Msi => [OsStr::new("/i"), path.as_os_str()]
|
||||
.into_iter()
|
||||
.chain(install_mode.msiexec_args().iter().map(OsStr::new))
|
||||
.chain(once(OsStr::new("/promptrestart")))
|
||||
.chain(self.installer_args())
|
||||
.collect(),
|
||||
};
|
||||
|
||||
if let Some(on_before_exit) = self.on_before_exit.as_ref() {
|
||||
on_before_exit();
|
||||
}
|
||||
|
||||
let parameters = installer_args.join(OsStr::new(" "));
|
||||
let parameters = encode_wide(parameters);
|
||||
|
||||
let path = match updater_type {
|
||||
WindowsUpdaterType::Msi => std::env::var("SYSTEMROOT").as_ref().map_or_else(
|
||||
|_| OsString::from("msiexec.exe"),
|
||||
|p| OsString::from(format!("{p}\\System32\\msiexec.exe")),
|
||||
),
|
||||
WindowsUpdaterType::Nsis => path.as_os_str().to_os_string(),
|
||||
};
|
||||
let file = encode_wide(path);
|
||||
let parameters = encode_wide(installer_args.join(OsStr::new(" ")));
|
||||
|
||||
unsafe {
|
||||
ShellExecuteW(
|
||||
0,
|
||||
@@ -591,6 +637,13 @@ impl Update {
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn nsis_installer_args(&self) -> Vec<&OsStr> {
|
||||
self.nsis_installer_args
|
||||
.iter()
|
||||
.map(OsStr::new)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn extract(bytes: &[u8]) -> Result<(WindowsUpdaterType, PathBuf, Option<tempfile::TempPath>)> {
|
||||
#[cfg(feature = "zip")]
|
||||
if infer::archive::is_zip(bytes) {
|
||||
|
||||
@@ -27,7 +27,7 @@ http = "1"
|
||||
rand = "0.8"
|
||||
futures-util = "0.3"
|
||||
tokio = { version = "1", features = [ "net", "sync" ] }
|
||||
tokio-tungstenite = { version = "0.21" }
|
||||
tokio-tungstenite = { version = "0.23" }
|
||||
|
||||
[features]
|
||||
default = [ "rustls-tls" ]
|
||||
|
||||
@@ -11,7 +11,7 @@ tauri = { workspace = true }
|
||||
tokio = { version = "1", features = ["net"] }
|
||||
futures-util = "0.3"
|
||||
tauri-plugin-websocket = { path = "../../../" }
|
||||
tokio-tungstenite = "0.21"
|
||||
tokio-tungstenite = "0.23"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { workspace = true }
|
||||
|
||||
Generated
+871
-1665
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"extends": ["config:base"],
|
||||
"baseBranches": ["v2", "v1"],
|
||||
"enabledManagers": ["cargo", "npm"],
|
||||
"semanticCommitType": "chore",
|
||||
"labels": ["dependencies"],
|
||||
@@ -28,6 +29,7 @@
|
||||
"description": "Prevent Renovate from replacing 2.0.0-beta.10 with 2.0.0-beta - REMOVE ONCE TAURI IS STABLE",
|
||||
"matchManagers": ["cargo"],
|
||||
"matchPackagePatterns": ["^tauri"],
|
||||
"matchCurrentValue": "2.*",
|
||||
"rangeStrategy": "bump"
|
||||
}
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user