Merge pull request #971 from tauri-apps/chore/tauri-beta-3

chore: update to tauri beta.4
pull/948/head
Lucas Fernandes Nogueira 1 year ago committed by GitHub
commit b1b9a02f9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -187,7 +187,6 @@
"path": "./plugins/log", "path": "./plugins/log",
"manager": "javascript" "manager": "javascript"
}, },
"nfc": { "nfc": {
"path": "./plugins/nfc", "path": "./plugins/nfc",
"manager": "rust" "manager": "rust"
@ -196,7 +195,6 @@
"path": "./plugins/nfc", "path": "./plugins/nfc",
"manager": "javascript" "manager": "javascript"
}, },
"notification": { "notification": {
"path": "./plugins/notification", "path": "./plugins/notification",
"manager": "rust" "manager": "rust"

@ -0,0 +1,32 @@
---
"authenticator": patch
"autostart": patch
"barcode-scanner": patch
"biometric": patch
"cli": patch
"clipboard-manager": patch
"deep-link": patch
"dialog": patch
"fs": patch
"global-shortcut": patch
"http": patch
"localhost": patch
"log-plugin": patch
"nfc": patch
"notification": patch
"os": patch
"persisted-scope": patch
"positioner": patch
"process": patch
"shell": patch
"single-instance": patch
"sql": patch
"store": patch
"stronghold": patch
"updater": patch
"upload": patch
"websocket": patch
"window-state": patch
---
Update MSRV to 1.75.

@ -1,95 +1,4 @@
{ {
"tag": "alpha", "tag": "beta",
"changes": [ "changes": [".changes/beta.md"]
".changes/alpha.16.md",
".changes/alpha.17.md",
".changes/alpha.18.md",
".changes/alpha.20.md",
".changes/api-alpha.11.md",
".changes/api-alpha.12.md",
".changes/api-alpha.13.md",
".changes/api-alpha.9.md",
".changes/beta.md",
".changes/biometric-initial-release.md",
".changes/deep-link-initial-release.md",
".changes/dialog-async-message-dialog.md",
".changes/dialog-default-path-windows-slash.md",
".changes/dialog-export-fileresponse.md",
".changes/dialog-message-android.md",
".changes/dialog-pin-rfd.md",
".changes/dialog-return-result.md",
".changes/fix-cmd-spawn-deadlock.md",
".changes/fix-debounced-event-type.md",
".changes/fix-deep-link-alpha-20.md",
".changes/fix-docs-build.md",
".changes/fix-emit-all-usage.md",
".changes/fix-fs-write-default-option.md",
".changes/fix-invoke-usage.md",
".changes/fix-notification-schedule-export.md",
".changes/fix-number-query-params.md",
".changes/fix-permission-notification.md",
".changes/fix-scheduled-notification.md",
".changes/fix-updater-macos.md",
".changes/fix-window-state-api.md",
".changes/fs-create-new.md",
".changes/fs-exists-2nd-arg.md",
".changes/fs-improve-error-message.md",
".changes/fs-improved-apis.md",
".changes/fs-replace-notify-debouncer.md",
".changes/fs-scope-tauri.md",
".changes/fs-trunacte-non-append.md",
".changes/fs-unwtach-rid.md",
".changes/fs-wiret-binary-file.md",
".changes/fs-write-panic.md",
".changes/global-shortcut-app-handle.md",
".changes/http-init-fetch-option-connectTimeout.md",
".changes/http-multipart-refactor.md",
".changes/http-plugin-refactor.md",
".changes/http-proxy-config.md",
".changes/http-remove-cmd-property.md",
".changes/http-response.md",
".changes/msrv-1.70.md",
".changes/nfc-initial-release.md",
".changes/notification-init-script.md",
".changes/notification-revert-sound.md",
".changes/notification-sound.md",
".changes/os-OsType.md",
".changes/os-plugin-refactor.md",
".changes/os-reading-undefined.md",
".changes/permissions.md",
".changes/persisted-scope-asset.md",
".changes/persisted-scope-glob.md",
".changes/positioner-tray-flag.md",
".changes/scanner-initial-release.md",
".changes/shell-command-apis.md",
".changes/shell-detached.md",
".changes/sql-decode-floats.md",
".changes/stronghold-arg-name.md",
".changes/stronghold-argon2.md",
".changes/stronghold-constructor.md",
".changes/tauri-alpha-15.md",
".changes/tauri-alpha.11.md",
".changes/tauri-alpha.12.md",
".changes/tauri-http-plugin-errror-invalid-rid.md",
".changes/tray-position.md",
".changes/updater-appimage.md",
".changes/updater-escaped-path.md",
".changes/updater-js-started-event.md",
".changes/updater-nsis-admin.md",
".changes/updater-nsis-basicui.md",
".changes/updater-nsis.md",
".changes/updater-on-chunk-fn-mut.md",
".changes/updater-plugin-refactor.md",
".changes/updater-proxy.md",
".changes/updater-string-replace-round2.md",
".changes/updater-string-replace.md",
".changes/updater.md",
".changes/upload-bufwriter.md",
".changes/v2-alpha.md",
".changes/websocket-fix-arg-name.md",
".changes/websocket-headers.md",
".changes/window-state-decorated.md",
".changes/window-state-promise.md",
".changes/window-state-zero-positions.md"
]
} }

@ -0,0 +1,57 @@
---
"authenticator": patch
"autostart": patch
"barcode-scanner": patch
"biometric": patch
"cli": patch
"clipboard-manager": patch
"deep-link": patch
"dialog": patch
"fs": patch
"global-shortcut": patch
"http": patch
"localhost": patch
"log-plugin": patch
"nfc": patch
"notification": patch
"os": patch
"persisted-scope": patch
"positioner": patch
"process": patch
"shell": patch
"single-instance": patch
"sql": patch
"store": patch
"stronghold": patch
"updater": patch
"upload": patch
"websocket": patch
"window-state": patch
"authenticator-js": patch
"autostart-js": patch
"barcode-scanner-js": patch
"biometric-js": patch
"cli-js": patch
"clipboard-manager-js": patch
"deep-link-js": patch
"dialog-js": patch
"fs-js": patch
"global-shortcut-js": patch
"http-js": patch
"log-js": patch
"nfc-js": patch
"notification-js": patch
"os-js": patch
"positioner-js": patch
"process-js": patch
"shell-js": patch
"sql-js": patch
"store-js": patch
"stronghold-js": patch
"updater-js": patch
"upload-js": patch
"websocket-js": patch
"window-state-js": patch
---
Update to tauri beta.4.

@ -175,7 +175,7 @@ jobs:
echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
vcpkg install openssl:x64-windows-static-md vcpkg install openssl:x64-windows-static-md
- uses: dtolnay/rust-toolchain@1.70.0 - uses: dtolnay/rust-toolchain@1.75.0
with: with:
targets: ${{ matrix.platform.target }} targets: ${{ matrix.platform.target }}

@ -27,12 +27,13 @@ const ignore = [
"api-iife.js", "api-iife.js",
"init-iife.js", "init-iife.js",
".build", ".build",
"notify_rust",
]; ];
async function checkFile(file) { async function checkFile(file) {
if ( if (
extensions.some((e) => file.endsWith(e)) && extensions.some((e) => file.endsWith(e)) &&
!ignore.some((i) => file.endsWith(i)) !ignore.some((i) => file.includes(`${path.sep}${i}`))
) { ) {
const fileStream = fs.createReadStream(file); const fileStream = fs.createReadStream(file);
const rl = readline.createInterface({ const rl = readline.createInterface({

1882
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -10,9 +10,9 @@ resolver = "2"
[workspace.dependencies] [workspace.dependencies]
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
log = "0.4" log = "0.4"
tauri = "2.0.0-beta.1" tauri = "2.0.0-beta.4"
tauri-build = "2.0.0-beta.0" tauri-build = "2.0.0-beta.3"
tauri-plugin = "2.0.0-beta.0" tauri-plugin = "2.0.0-beta.3"
serde_json = "1" serde_json = "1"
thiserror = "1" thiserror = "1"
url = "2" url = "2"
@ -22,7 +22,7 @@ schemars = "0.8"
edition = "2021" edition = "2021"
authors = ["Tauri Programme within The Commons Conservancy"] authors = ["Tauri Programme within The Commons Conservancy"]
license = "Apache-2.0 OR MIT" license = "Apache-2.0 OR MIT"
rust-version = "1.70" rust-version = "1.75"
# default to small, optimized release binaries # default to small, optimized release binaries
[profile.release] [profile.release]

@ -31,7 +31,7 @@
| [websocket](plugins/websocket) | Open a WebSocket connection using a Rust client in JS. | ✅ | ✅ | ✅ | ? | ? | | [websocket](plugins/websocket) | Open a WebSocket connection using a Rust client in JS. | ✅ | ✅ | ✅ | ? | ? |
| [window-state](plugins/window-state) | Persist window sizes and positions. | ✅ | ✅ | ✅ | ? | ? | | [window-state](plugins/window-state) | Persist window sizes and positions. | ✅ | ✅ | ✅ | ? | ? |
_This repo and all plugins require a Rust version of at least **1.70**_ _This repo and all plugins require a Rust version of at least **1.75**_
## Partners ## Partners

@ -9,7 +9,7 @@
"serve": "vite preview" "serve": "vite preview"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0", "@tauri-apps/api": "2.0.0-beta.2",
"@tauri-apps/plugin-barcode-scanner": "2.0.0-beta.0", "@tauri-apps/plugin-barcode-scanner": "2.0.0-beta.0",
"@tauri-apps/plugin-biometric": "2.0.0-beta.0", "@tauri-apps/plugin-biometric": "2.0.0-beta.0",
"@tauri-apps/plugin-cli": "2.0.0-beta.0", "@tauri-apps/plugin-cli": "2.0.0-beta.0",
@ -30,7 +30,7 @@
"@iconify-json/codicon": "^1.1.37", "@iconify-json/codicon": "^1.1.37",
"@iconify-json/ph": "^1.1.8", "@iconify-json/ph": "^1.1.8",
"@sveltejs/vite-plugin-svelte": "^3.0.1", "@sveltejs/vite-plugin-svelte": "^3.0.1",
"@tauri-apps/cli": "2.0.0-beta.0", "@tauri-apps/cli": "2.0.0-beta.3",
"@unocss/extractor-svelte": "^0.58.0", "@unocss/extractor-svelte": "^0.58.0",
"internal-ip": "^8.0.0", "internal-ip": "^8.0.0",
"svelte": "^4.2.8", "svelte": "^4.2.8",

@ -4,3 +4,7 @@
# cargo-mobile # cargo-mobile
.cargo/ .cargo/
gen/schemas/*.json
!gen/schemas/desktop-schema.json
!gen/schemas/mobile-schema.json

@ -1,5 +1,5 @@
{ {
"$schema": "schemas/desktop-schema.json", "$schema": "../gen/schemas/desktop-schema.json",
"identifier": "run-app-base", "identifier": "run-app-base",
"description": "Base permissions to run the app", "description": "Base permissions to run the app",
"windows": ["main"], "windows": ["main"],

@ -1,5 +1,5 @@
{ {
"$schema": "./schemas/desktop-schema.json", "$schema": "../gen/schemas/desktop-schema.json",
"identifier": "run-app-desktop", "identifier": "run-app-desktop",
"description": "Permissions to run the app (desktop only)", "description": "Permissions to run the app (desktop only)",
"windows": ["main"], "windows": ["main"],

@ -1,5 +1,5 @@
{ {
"$schema": "./schemas/mobile-schema.json", "$schema": "../gen/schemas/mobile-schema.json",
"identifier": "run-app-mobile", "identifier": "run-app-mobile",
"description": "Permissions to run the app (mobile only)", "description": "Permissions to run the app (mobile only)",
"windows": ["main"], "windows": ["main"],

@ -1,3 +0,0 @@
*.json
!desktop-schema.json
!mobile-schema.json

@ -48,7 +48,7 @@
"type": "string" "type": "string"
}, },
"context": { "context": {
"description": "Execution context of the capability.\n\nAt runtime, Tauri filters the IPC command together with the context to determine wheter it is allowed or not and its scope.", "description": "Execution context of the capability.\n\nAt runtime, Tauri filters the IPC command together with the context to determine whether it is allowed or not and its scope.",
"default": "local", "default": "local",
"allOf": [ "allOf": [
{ {
@ -57,7 +57,14 @@
] ]
}, },
"windows": { "windows": {
"description": "List of windows that uses this capability. Can be a glob pattern.", "description": "List of windows that uses this capability. Can be a glob pattern.\n\nOn multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.",
"type": "array",
"items": {
"type": "string"
}
},
"webviews": {
"description": "List of webviews that uses this capability. Can be a glob pattern.\n\nThis is only required when using on multiwebview contexts, by default all child webviews of a window that matches [`Self::windows`] are linked.",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
@ -106,10 +113,10 @@
"remote": { "remote": {
"type": "object", "type": "object",
"required": [ "required": [
"domains" "urls"
], ],
"properties": { "properties": {
"domains": { "urls": {
"description": "Remote domains this capability refers to. Can use glob patterns.", "description": "Remote domains this capability refers to. Can use glob patterns.",
"type": "array", "type": "array",
"items": { "items": {
@ -2147,8 +2154,7 @@
"path": { "path": {
"type": "string" "type": "string"
} }
}, }
"$schema": "http://json-schema.org/draft-07/schema#"
} }
}, },
"deny": { "deny": {
@ -2162,8 +2168,7 @@
"path": { "path": {
"type": "string" "type": "string"
} }
}, }
"$schema": "http://json-schema.org/draft-07/schema#"
} }
} }
} }
@ -2254,8 +2259,7 @@
"description": "A URL that can be accessed by the webview when using the HTTP APIs. The scoped URL is matched against the request URL using a glob pattern.\n\nExamples:\n\n- \"https://*\" or \"https://**\" : allows all HTTPS urls\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", "description": "A URL that can be accessed by the webview when using the HTTP APIs. The scoped URL is matched against the request URL using a glob pattern.\n\nExamples:\n\n- \"https://*\" or \"https://**\" : allows all HTTPS urls\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
"type": "string" "type": "string"
} }
}, }
"$schema": "http://json-schema.org/draft-07/schema#"
} }
}, },
"deny": { "deny": {
@ -2271,8 +2275,7 @@
"description": "A URL that can be accessed by the webview when using the HTTP APIs. The scoped URL is matched against the request URL using a glob pattern.\n\nExamples:\n\n- \"https://*\" or \"https://**\" : allows all HTTPS urls\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", "description": "A URL that can be accessed by the webview when using the HTTP APIs. The scoped URL is matched against the request URL using a glob pattern.\n\nExamples:\n\n- \"https://*\" or \"https://**\" : allows all HTTPS urls\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
"type": "string" "type": "string"
} }
}, }
"$schema": "http://json-schema.org/draft-07/schema#"
} }
} }
} }
@ -2375,48 +2378,6 @@
"description": "If this command is a sidecar command.", "description": "If this command is a sidecar command.",
"type": "boolean" "type": "boolean"
} }
},
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ShellAllowedArg": {
"anyOf": [
{
"description": "A non-configurable argument that is passed to the command in the order it was specified.",
"type": "string"
},
{
"additionalProperties": false,
"description": "A variable that is set while calling the command from the webview API.",
"properties": {
"validator": {
"description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\n[regex]: https://docs.rs/regex/latest/regex/#syntax",
"type": "string"
}
},
"required": [
"validator"
],
"type": "object"
}
],
"description": "A command argument allowed to be executed by the webview API."
},
"ShellAllowedArgs": {
"anyOf": [
{
"description": "Use a simple boolean to allow all or disable all arguments to this command configuration.",
"type": "boolean"
},
{
"description": "A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.",
"items": {
"$ref": "#/definitions/ShellAllowedArg"
},
"type": "array"
}
],
"description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration."
}
} }
} }
}, },
@ -2452,48 +2413,6 @@
"description": "If this command is a sidecar command.", "description": "If this command is a sidecar command.",
"type": "boolean" "type": "boolean"
} }
},
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ShellAllowedArg": {
"anyOf": [
{
"description": "A non-configurable argument that is passed to the command in the order it was specified.",
"type": "string"
},
{
"additionalProperties": false,
"description": "A variable that is set while calling the command from the webview API.",
"properties": {
"validator": {
"description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\n[regex]: https://docs.rs/regex/latest/regex/#syntax",
"type": "string"
}
},
"required": [
"validator"
],
"type": "object"
}
],
"description": "A command argument allowed to be executed by the webview API."
},
"ShellAllowedArgs": {
"anyOf": [
{
"description": "Use a simple boolean to allow all or disable all arguments to this command configuration.",
"type": "boolean"
},
{
"description": "A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.",
"items": {
"$ref": "#/definitions/ShellAllowedArg"
},
"type": "array"
}
],
"description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration."
}
} }
} }
} }
@ -2504,7 +2423,6 @@
] ]
}, },
"Identifier": { "Identifier": {
"description": "Permission identifier",
"oneOf": [ "oneOf": [
{ {
"description": "app:default -> Default permissions for the plugin.", "description": "app:default -> Default permissions for the plugin.",
@ -5992,20 +5910,6 @@
"window:allow-inner-size" "window:allow-inner-size"
] ]
}, },
{
"description": "window:allow-internal-on-mousedown -> Enables the internal_on_mousedown command without any pre-configured scope.",
"type": "string",
"enum": [
"window:allow-internal-on-mousedown"
]
},
{
"description": "window:allow-internal-on-mousemove -> Enables the internal_on_mousemove command without any pre-configured scope.",
"type": "string",
"enum": [
"window:allow-internal-on-mousemove"
]
},
{ {
"description": "window:allow-internal-toggle-maximize -> Enables the internal_toggle_maximize command without any pre-configured scope.", "description": "window:allow-internal-toggle-maximize -> Enables the internal_toggle_maximize command without any pre-configured scope.",
"type": "string", "type": "string",
@ -6426,20 +6330,6 @@
"window:deny-inner-size" "window:deny-inner-size"
] ]
}, },
{
"description": "window:deny-internal-on-mousedown -> Denies the internal_on_mousedown command without any pre-configured scope.",
"type": "string",
"enum": [
"window:deny-internal-on-mousedown"
]
},
{
"description": "window:deny-internal-on-mousemove -> Denies the internal_on_mousemove command without any pre-configured scope.",
"type": "string",
"enum": [
"window:deny-internal-on-mousemove"
]
},
{ {
"description": "window:deny-internal-toggle-maximize -> Denies the internal_toggle_maximize command without any pre-configured scope.", "description": "window:deny-internal-toggle-maximize -> Denies the internal_toggle_maximize command without any pre-configured scope.",
"type": "string", "type": "string",
@ -6892,6 +6782,45 @@
] ]
} }
] ]
},
"ShellAllowedArg": {
"description": "A command argument allowed to be executed by the webview API.",
"anyOf": [
{
"description": "A non-configurable argument that is passed to the command in the order it was specified.",
"type": "string"
},
{
"description": "A variable that is set while calling the command from the webview API.",
"type": "object",
"required": [
"validator"
],
"properties": {
"validator": {
"description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\n[regex]: https://docs.rs/regex/latest/regex/#syntax",
"type": "string"
}
},
"additionalProperties": false
}
]
},
"ShellAllowedArgs": {
"description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.",
"anyOf": [
{
"description": "Use a simple boolean to allow all or disable all arguments to this command configuration.",
"type": "boolean"
},
{
"description": "A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.",
"type": "array",
"items": {
"$ref": "#/definitions/ShellAllowedArg"
}
}
]
} }
} }
} }

@ -48,7 +48,7 @@
"type": "string" "type": "string"
}, },
"context": { "context": {
"description": "Execution context of the capability.\n\nAt runtime, Tauri filters the IPC command together with the context to determine wheter it is allowed or not and its scope.", "description": "Execution context of the capability.\n\nAt runtime, Tauri filters the IPC command together with the context to determine whether it is allowed or not and its scope.",
"default": "local", "default": "local",
"allOf": [ "allOf": [
{ {
@ -57,7 +57,14 @@
] ]
}, },
"windows": { "windows": {
"description": "List of windows that uses this capability. Can be a glob pattern.", "description": "List of windows that uses this capability. Can be a glob pattern.\n\nOn multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.",
"type": "array",
"items": {
"type": "string"
}
},
"webviews": {
"description": "List of webviews that uses this capability. Can be a glob pattern.\n\nThis is only required when using on multiwebview contexts, by default all child webviews of a window that matches [`Self::windows`] are linked.",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
@ -106,10 +113,10 @@
"remote": { "remote": {
"type": "object", "type": "object",
"required": [ "required": [
"domains" "urls"
], ],
"properties": { "properties": {
"domains": { "urls": {
"description": "Remote domains this capability refers to. Can use glob patterns.", "description": "Remote domains this capability refers to. Can use glob patterns.",
"type": "array", "type": "array",
"items": { "items": {
@ -2147,8 +2154,7 @@
"path": { "path": {
"type": "string" "type": "string"
} }
}, }
"$schema": "http://json-schema.org/draft-07/schema#"
} }
}, },
"deny": { "deny": {
@ -2162,8 +2168,7 @@
"path": { "path": {
"type": "string" "type": "string"
} }
}, }
"$schema": "http://json-schema.org/draft-07/schema#"
} }
} }
} }
@ -2254,8 +2259,7 @@
"description": "A URL that can be accessed by the webview when using the HTTP APIs. The scoped URL is matched against the request URL using a glob pattern.\n\nExamples:\n\n- \"https://*\" or \"https://**\" : allows all HTTPS urls\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", "description": "A URL that can be accessed by the webview when using the HTTP APIs. The scoped URL is matched against the request URL using a glob pattern.\n\nExamples:\n\n- \"https://*\" or \"https://**\" : allows all HTTPS urls\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
"type": "string" "type": "string"
} }
}, }
"$schema": "http://json-schema.org/draft-07/schema#"
} }
}, },
"deny": { "deny": {
@ -2271,8 +2275,7 @@
"description": "A URL that can be accessed by the webview when using the HTTP APIs. The scoped URL is matched against the request URL using a glob pattern.\n\nExamples:\n\n- \"https://*\" or \"https://**\" : allows all HTTPS urls\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", "description": "A URL that can be accessed by the webview when using the HTTP APIs. The scoped URL is matched against the request URL using a glob pattern.\n\nExamples:\n\n- \"https://*\" or \"https://**\" : allows all HTTPS urls\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
"type": "string" "type": "string"
} }
}, }
"$schema": "http://json-schema.org/draft-07/schema#"
} }
} }
} }
@ -2375,48 +2378,6 @@
"description": "If this command is a sidecar command.", "description": "If this command is a sidecar command.",
"type": "boolean" "type": "boolean"
} }
},
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ShellAllowedArg": {
"anyOf": [
{
"description": "A non-configurable argument that is passed to the command in the order it was specified.",
"type": "string"
},
{
"additionalProperties": false,
"description": "A variable that is set while calling the command from the webview API.",
"properties": {
"validator": {
"description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\n[regex]: https://docs.rs/regex/latest/regex/#syntax",
"type": "string"
}
},
"required": [
"validator"
],
"type": "object"
}
],
"description": "A command argument allowed to be executed by the webview API."
},
"ShellAllowedArgs": {
"anyOf": [
{
"description": "Use a simple boolean to allow all or disable all arguments to this command configuration.",
"type": "boolean"
},
{
"description": "A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.",
"items": {
"$ref": "#/definitions/ShellAllowedArg"
},
"type": "array"
}
],
"description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration."
}
} }
} }
}, },
@ -2452,48 +2413,6 @@
"description": "If this command is a sidecar command.", "description": "If this command is a sidecar command.",
"type": "boolean" "type": "boolean"
} }
},
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ShellAllowedArg": {
"anyOf": [
{
"description": "A non-configurable argument that is passed to the command in the order it was specified.",
"type": "string"
},
{
"additionalProperties": false,
"description": "A variable that is set while calling the command from the webview API.",
"properties": {
"validator": {
"description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\n[regex]: https://docs.rs/regex/latest/regex/#syntax",
"type": "string"
}
},
"required": [
"validator"
],
"type": "object"
}
],
"description": "A command argument allowed to be executed by the webview API."
},
"ShellAllowedArgs": {
"anyOf": [
{
"description": "Use a simple boolean to allow all or disable all arguments to this command configuration.",
"type": "boolean"
},
{
"description": "A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.",
"items": {
"$ref": "#/definitions/ShellAllowedArg"
},
"type": "array"
}
],
"description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration."
}
} }
} }
} }
@ -2504,7 +2423,6 @@
] ]
}, },
"Identifier": { "Identifier": {
"description": "Permission identifier",
"oneOf": [ "oneOf": [
{ {
"description": "app:default -> Default permissions for the plugin.", "description": "app:default -> Default permissions for the plugin.",
@ -6020,20 +5938,6 @@
"window:allow-inner-size" "window:allow-inner-size"
] ]
}, },
{
"description": "window:allow-internal-on-mousedown -> Enables the internal_on_mousedown command without any pre-configured scope.",
"type": "string",
"enum": [
"window:allow-internal-on-mousedown"
]
},
{
"description": "window:allow-internal-on-mousemove -> Enables the internal_on_mousemove command without any pre-configured scope.",
"type": "string",
"enum": [
"window:allow-internal-on-mousemove"
]
},
{ {
"description": "window:allow-internal-toggle-maximize -> Enables the internal_toggle_maximize command without any pre-configured scope.", "description": "window:allow-internal-toggle-maximize -> Enables the internal_toggle_maximize command without any pre-configured scope.",
"type": "string", "type": "string",
@ -6454,20 +6358,6 @@
"window:deny-inner-size" "window:deny-inner-size"
] ]
}, },
{
"description": "window:deny-internal-on-mousedown -> Denies the internal_on_mousedown command without any pre-configured scope.",
"type": "string",
"enum": [
"window:deny-internal-on-mousedown"
]
},
{
"description": "window:deny-internal-on-mousemove -> Denies the internal_on_mousemove command without any pre-configured scope.",
"type": "string",
"enum": [
"window:deny-internal-on-mousemove"
]
},
{ {
"description": "window:deny-internal-toggle-maximize -> Denies the internal_toggle_maximize command without any pre-configured scope.", "description": "window:deny-internal-toggle-maximize -> Denies the internal_toggle_maximize command without any pre-configured scope.",
"type": "string", "type": "string",
@ -6920,6 +6810,45 @@
] ]
} }
] ]
},
"ShellAllowedArg": {
"description": "A command argument allowed to be executed by the webview API.",
"anyOf": [
{
"description": "A non-configurable argument that is passed to the command in the order it was specified.",
"type": "string"
},
{
"description": "A variable that is set while calling the command from the webview API.",
"type": "object",
"required": [
"validator"
],
"properties": {
"validator": {
"description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\n[regex]: https://docs.rs/regex/latest/regex/#syntax",
"type": "string"
}
},
"additionalProperties": false
}
]
},
"ShellAllowedArgs": {
"description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.",
"anyOf": [
{
"description": "Use a simple boolean to allow all or disable all arguments to this command configuration.",
"type": "boolean"
},
{
"description": "A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.",
"type": "array",
"items": {
"$ref": "#/definitions/ShellAllowedArg"
}
}
]
} }
} }
} }

@ -6,7 +6,7 @@ Use hardware security-keys in your Tauri App.
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -4,7 +4,7 @@ Automatically launch your application at startup. Supports Windows, Mac (via App
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -23,6 +23,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -27,6 +27,6 @@
"tslib": "2.6.0" "tslib": "2.6.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -6,7 +6,7 @@ Parse arguments from your Command Line Interface.
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -23,6 +23,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -4,7 +4,7 @@ Read and write to the system clipboard.
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -23,6 +23,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -4,7 +4,7 @@ Set your Tauri application as the default handler for an URL.
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -10,11 +10,11 @@
"tauri": "tauri" "tauri": "tauri"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0", "@tauri-apps/api": "2.0.0-beta.2",
"@tauri-apps/plugin-deep-link": "2.0.0-beta.0" "@tauri-apps/plugin-deep-link": "2.0.0-beta.0"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "2.0.0-beta.0", "@tauri-apps/cli": "2.0.0-beta.3",
"internal-ip": "^8.0.0", "internal-ip": "^8.0.0",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^5.0.12" "vite": "^5.0.12"

@ -2,6 +2,6 @@
# will have compiled files and executables # will have compiled files and executables
/target/ /target/
/capabilities/schemas /gen/schemas
.cargo .cargo

@ -6,7 +6,7 @@ authors = ["you"]
license = "" license = ""
repository = "" repository = ""
edition = "2021" edition = "2021"
rust-version = "1.70" rust-version = "1.75"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -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 t(e,n={},t){return window.__TAURI_INTERNALS__.invoke(e,n,t)}var r;async function _(e,r,_){const i="string"==typeof _?.target?{kind:"AnyLabel",label:_.target}:_?.target??{kind:"Any"};return t("plugin:event|listen",{event:e,target:i,handler:n(r)}).then((n=>async()=>async function(e,n){await t("plugin:event|unlisten",{event:e,eventId:n})}(e,n)))}async function i(){return await t("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.WEBVIEW_CREATED="tauri://webview-created",e.WEBVIEW_FILE_DROP="tauri://file-drop",e.WEBVIEW_FILE_DROP_HOVER="tauri://file-drop-hover",e.WEBVIEW_FILE_DROP_CANCELLED="tauri://file-drop-cancelled"}(r||(r={})),e.getCurrent=i,e.onOpenUrl=async function(e){const n=await i();return null!=n&&e(n),await _("deep-link://new-url",(n=>e(n.payload)))},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 t(e,n={},t){return window.__TAURI_INTERNALS__.invoke(e,n,t)}var r;async function i(e,r,i){const _="string"==typeof i?.target?{kind:"AnyLabel",label:i.target}:i?.target??{kind:"Any"};return t("plugin:event|listen",{event:e,target:_,handler:n(r)}).then((n=>async()=>async function(e,n){await t("plugin:event|unlisten",{event:e,eventId:n})}(e,n)))}async function _(){return await t("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.WEBVIEW_CREATED="tauri://webview-created",e.FILE_DROP="tauri://file-drop",e.FILE_DROP_HOVER="tauri://file-drop-hover",e.FILE_DROP_CANCELLED="tauri://file-drop-cancelled"}(r||(r={})),e.getCurrent=_,e.onOpenUrl=async function(e){const n=await _();return null!=n&&e(n),await i("deep-link://new-url",(n=>e(n.payload)))},e}({});Object.defineProperty(window.__TAURI__,"deepLink",{value:__TAURI_PLUGIN_DEEPLINK__})}

@ -28,5 +28,5 @@ tauri-plugin-fs = { path = "../fs", version = "2.0.0-beta.0" }
glib = "0.16" glib = "0.16"
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] [target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
rfd = { version = "=0.12.0", features = [ "gtk3", "common-controls-v6" ] } rfd = { version = "0.14", default-features = false, features = [ "tokio", "gtk3", "common-controls-v6" ] }
raw-window-handle = "0.5" raw-window-handle = "0.6"

@ -4,7 +4,7 @@ Native system dialogs for opening and saving files along with message dialogs.
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -23,6 +23,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -10,7 +10,7 @@
use std::path::PathBuf; use std::path::PathBuf;
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; use raw_window_handle::{HasWindowHandle, RawWindowHandle};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use tauri::{plugin::PluginApi, AppHandle, Runtime}; use tauri::{plugin::PluginApi, AppHandle, Runtime};
@ -97,9 +97,11 @@ impl From<MessageDialogKind> for rfd::MessageLevel {
struct WindowHandle(RawWindowHandle); struct WindowHandle(RawWindowHandle);
unsafe impl HasRawWindowHandle for WindowHandle { impl HasWindowHandle for WindowHandle {
fn raw_window_handle(&self) -> RawWindowHandle { fn window_handle(
self.0 &self,
) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(self.0) })
} }
} }

@ -178,8 +178,10 @@ impl<R: Runtime> MessageDialogBuilder<R> {
/// ///
/// - **Linux:** Unsupported. /// - **Linux:** Unsupported.
#[cfg(desktop)] #[cfg(desktop)]
pub fn parent<W: raw_window_handle::HasRawWindowHandle>(mut self, parent: &W) -> Self { pub fn parent<W: raw_window_handle::HasWindowHandle>(mut self, parent: &W) -> Self {
self.parent.replace(parent.raw_window_handle()); if let Ok(h) = parent.window_handle() {
self.parent.replace(h.as_raw());
}
self self
} }
@ -329,8 +331,10 @@ impl<R: Runtime> FileDialogBuilder<R> {
/// Sets the parent window of the dialog. /// Sets the parent window of the dialog.
#[cfg(desktop)] #[cfg(desktop)]
#[must_use] #[must_use]
pub fn set_parent<W: raw_window_handle::HasRawWindowHandle>(mut self, parent: &W) -> Self { pub fn set_parent<W: raw_window_handle::HasWindowHandle>(mut self, parent: &W) -> Self {
self.parent.replace(parent.raw_window_handle()); if let Ok(h) = parent.window_handle() {
self.parent.replace(h.as_raw());
}
self self
} }

@ -4,7 +4,7 @@ Access the file system.
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -73,8 +73,8 @@ pub struct BaseOptions {
#[tauri::command] #[tauri::command]
pub fn create<R: Runtime>( pub fn create<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
path: SafePathBuf, path: SafePathBuf,
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<ResourceId> { ) -> CommandResult<ResourceId> {
@ -123,8 +123,8 @@ fn default_true() -> bool {
#[tauri::command] #[tauri::command]
pub fn open<R: Runtime>( pub fn open<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
path: SafePathBuf, path: SafePathBuf,
options: Option<OpenOptions>, options: Option<OpenOptions>,
) -> CommandResult<ResourceId> { ) -> CommandResult<ResourceId> {
@ -184,8 +184,8 @@ pub struct CopyFileOptions {
#[tauri::command] #[tauri::command]
pub fn copy_file<R: Runtime>( pub fn copy_file<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
from_path: SafePathBuf, from_path: SafePathBuf,
to_path: SafePathBuf, to_path: SafePathBuf,
options: Option<CopyFileOptions>, options: Option<CopyFileOptions>,
@ -226,8 +226,8 @@ pub struct MkdirOptions {
#[tauri::command] #[tauri::command]
pub fn mkdir<R: Runtime>( pub fn mkdir<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
path: SafePathBuf, path: SafePathBuf,
options: Option<MkdirOptions>, options: Option<MkdirOptions>,
) -> CommandResult<()> { ) -> CommandResult<()> {
@ -293,8 +293,8 @@ fn read_dir_inner<P: AsRef<Path>>(path: P) -> crate::Result<Vec<DirEntry>> {
#[tauri::command] #[tauri::command]
pub fn read_dir<R: Runtime>( pub fn read_dir<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
path: SafePathBuf, path: SafePathBuf,
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<Vec<DirEntry>> { ) -> CommandResult<Vec<DirEntry>> {
@ -332,8 +332,8 @@ pub fn read<R: Runtime>(
#[tauri::command] #[tauri::command]
pub fn read_file<R: Runtime>( pub fn read_file<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
path: SafePathBuf, path: SafePathBuf,
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<Vec<u8>> { ) -> CommandResult<Vec<u8>> {
@ -357,8 +357,8 @@ pub fn read_file<R: Runtime>(
#[tauri::command] #[tauri::command]
pub fn read_text_file<R: Runtime>( pub fn read_text_file<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
path: SafePathBuf, path: SafePathBuf,
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<String> { ) -> CommandResult<String> {
@ -382,8 +382,8 @@ pub fn read_text_file<R: Runtime>(
#[tauri::command] #[tauri::command]
pub fn read_text_file_lines<R: Runtime>( pub fn read_text_file_lines<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
path: SafePathBuf, path: SafePathBuf,
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<ResourceId> { ) -> CommandResult<ResourceId> {
@ -438,8 +438,8 @@ pub struct RemoveOptions {
#[tauri::command] #[tauri::command]
pub fn remove<R: Runtime>( pub fn remove<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
path: SafePathBuf, path: SafePathBuf,
options: Option<RemoveOptions>, options: Option<RemoveOptions>,
) -> CommandResult<()> { ) -> CommandResult<()> {
@ -506,8 +506,8 @@ pub struct RenameOptions {
#[tauri::command] #[tauri::command]
pub fn rename<R: Runtime>( pub fn rename<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
old_path: SafePathBuf, old_path: SafePathBuf,
new_path: SafePathBuf, new_path: SafePathBuf,
options: Option<RenameOptions>, options: Option<RenameOptions>,
@ -568,8 +568,8 @@ pub fn seek<R: Runtime>(
#[tauri::command] #[tauri::command]
pub fn stat<R: Runtime>( pub fn stat<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
path: SafePathBuf, path: SafePathBuf,
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<FileInfo> { ) -> CommandResult<FileInfo> {
@ -592,8 +592,8 @@ pub fn stat<R: Runtime>(
#[tauri::command] #[tauri::command]
pub fn lstat<R: Runtime>( pub fn lstat<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
path: SafePathBuf, path: SafePathBuf,
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<FileInfo> { ) -> CommandResult<FileInfo> {
@ -624,8 +624,8 @@ pub fn fstat<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> CommandResult<Fi
#[tauri::command] #[tauri::command]
pub fn truncate<R: Runtime>( pub fn truncate<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
path: SafePathBuf, path: SafePathBuf,
len: Option<u64>, len: Option<u64>,
options: Option<BaseOptions>, options: Option<BaseOptions>,
@ -701,8 +701,8 @@ fn default_create_value() -> bool {
fn write_file_inner<R: Runtime>( fn write_file_inner<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: &GlobalScope<'_, Entry>, global_scope: &GlobalScope<Entry>,
command_scope: &CommandScope<'_, Entry>, command_scope: &CommandScope<Entry>,
path: SafePathBuf, path: SafePathBuf,
data: &[u8], data: &[u8],
options: Option<WriteFileOptions>, options: Option<WriteFileOptions>,
@ -754,8 +754,8 @@ fn write_file_inner<R: Runtime>(
#[tauri::command] #[tauri::command]
pub fn write_file<R: Runtime>( pub fn write_file<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
path: SafePathBuf, path: SafePathBuf,
data: Vec<u8>, data: Vec<u8>,
options: Option<WriteFileOptions>, options: Option<WriteFileOptions>,
@ -766,8 +766,8 @@ pub fn write_file<R: Runtime>(
#[tauri::command] #[tauri::command]
pub fn write_text_file<R: Runtime>( pub fn write_text_file<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
path: SafePathBuf, path: SafePathBuf,
data: String, data: String,
options: Option<WriteFileOptions>, options: Option<WriteFileOptions>,
@ -785,8 +785,8 @@ pub fn write_text_file<R: Runtime>(
#[tauri::command] #[tauri::command]
pub fn exists<R: Runtime>( pub fn exists<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
path: SafePathBuf, path: SafePathBuf,
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<bool> { ) -> CommandResult<bool> {
@ -802,8 +802,8 @@ pub fn exists<R: Runtime>(
pub fn resolve_path<R: Runtime>( pub fn resolve_path<R: Runtime>(
app: &AppHandle<R>, app: &AppHandle<R>,
global_scope: &GlobalScope<'_, Entry>, global_scope: &GlobalScope<Entry>,
command_scope: &CommandScope<'_, Entry>, command_scope: &CommandScope<Entry>,
path: SafePathBuf, path: SafePathBuf,
base_dir: Option<BaseDirectory>, base_dir: Option<BaseDirectory>,
) -> CommandResult<PathBuf> { ) -> CommandResult<PathBuf> {

@ -86,8 +86,8 @@ pub async fn watch<R: Runtime>(
paths: Vec<SafePathBuf>, paths: Vec<SafePathBuf>,
options: WatchOptions, options: WatchOptions,
on_event: Channel, on_event: Channel,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
) -> CommandResult<ResourceId> { ) -> CommandResult<ResourceId> {
let mut resolved_paths = Vec::with_capacity(paths.capacity()); let mut resolved_paths = Vec::with_capacity(paths.capacity());
for path in paths { for path in paths {

@ -6,7 +6,7 @@ Register global shortcuts.
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -23,6 +23,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -4,7 +4,7 @@ Access the HTTP client written in Rust.
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -23,6 +23,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -139,8 +139,8 @@ fn attach_proxy(
pub async fn fetch<R: Runtime>( pub async fn fetch<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
client_config: ClientConfig, client_config: ClientConfig,
command_scope: CommandScope<'_, Entry>, command_scope: CommandScope<Entry>,
global_scope: GlobalScope<'_, Entry>, global_scope: GlobalScope<Entry>,
) -> crate::Result<ResourceId> { ) -> crate::Result<ResourceId> {
let ClientConfig { let ClientConfig {
method, method,

@ -19,7 +19,7 @@
## \[2.0.0-alpha.3] ## \[2.0.0-alpha.3]
- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update MSRV to 1.70. - [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update MSRV to 1.75.
## \[2.0.0-alpha.2] ## \[2.0.0-alpha.2]

@ -6,7 +6,7 @@ Expose your apps assets through a localhost server instead of the default custom
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -4,7 +4,7 @@ Configurable logging for your Tauri app.
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_LOG__=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 a,t;async function o(e,a,t){const o="string"==typeof t?.target?{kind:"AnyLabel",label:t.target}:t?.target??{kind:"Any"};return r("plugin:event|listen",{event:e,target:o,handler:n(a)}).then((n=>async()=>async function(e,n){await r("plugin:event|unlisten",{event:e,eventId:n})}(e,n)))}async function i(e,n,a){const t=(new Error).stack?.split("\n").map((e=>e.split("@"))),o=t?.filter((([e,n])=>e.length>0&&"[native code]"!==n)),{file:i,line:c,keyValues:l}=a??{};let u=o?.[0]?.filter((e=>e.length>0)).join("@");"Error"===u&&(u="webview::unknown"),await r("plugin:log|log",{level:e,message:n,location:u,file:i,line:c,keyValues:l})}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.WEBVIEW_CREATED="tauri://webview-created",e.WEBVIEW_FILE_DROP="tauri://file-drop",e.WEBVIEW_FILE_DROP_HOVER="tauri://file-drop-hover",e.WEBVIEW_FILE_DROP_CANCELLED="tauri://file-drop-cancelled"}(a||(a={})),function(e){e[e.Trace=1]="Trace",e[e.Debug=2]="Debug",e[e.Info=3]="Info",e[e.Warn=4]="Warn",e[e.Error=5]="Error"}(t||(t={})),e.attachConsole=async function(){return await o("log://log",(e=>{const n=e.payload,r=n.message.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,"");switch(n.level){case t.Trace:console.log(r);break;case t.Debug:console.debug(r);break;case t.Info:console.info(r);break;case t.Warn:console.warn(r);break;case t.Error:console.error(r);break;default:throw new Error(`unknown log level ${n.level}`)}}))},e.debug=async function(e,n){await i(t.Debug,e,n)},e.error=async function(e,n){await i(t.Error,e,n)},e.info=async function(e,n){await i(t.Info,e,n)},e.trace=async function(e,n){await i(t.Trace,e,n)},e.warn=async function(e,n){await i(t.Warn,e,n)},e}({});Object.defineProperty(window.__TAURI__,"log",{value:__TAURI_PLUGIN_LOG__})} if("__TAURI__"in window){var __TAURI_PLUGIN_LOG__=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 a,t;async function o(e,a,t){const o="string"==typeof t?.target?{kind:"AnyLabel",label:t.target}:t?.target??{kind:"Any"};return r("plugin:event|listen",{event:e,target:o,handler:n(a)}).then((n=>async()=>async function(e,n){await r("plugin:event|unlisten",{event:e,eventId:n})}(e,n)))}async function i(e,n,a){const t=(new Error).stack?.split("\n").map((e=>e.split("@"))),o=t?.filter((([e,n])=>e.length>0&&"[native code]"!==n)),{file:i,line:c,keyValues:l}=a??{};let u=o?.[0]?.filter((e=>e.length>0)).join("@");"Error"===u&&(u="webview::unknown"),await r("plugin:log|log",{level:e,message:n,location:u,file:i,line:c,keyValues:l})}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.WEBVIEW_CREATED="tauri://webview-created",e.FILE_DROP="tauri://file-drop",e.FILE_DROP_HOVER="tauri://file-drop-hover",e.FILE_DROP_CANCELLED="tauri://file-drop-cancelled"}(a||(a={})),function(e){e[e.Trace=1]="Trace",e[e.Debug=2]="Debug",e[e.Info=3]="Info",e[e.Warn=4]="Warn",e[e.Error=5]="Error"}(t||(t={})),e.attachConsole=async function(){return await o("log://log",(e=>{const n=e.payload,r=n.message.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,"");switch(n.level){case t.Trace:console.log(r);break;case t.Debug:console.debug(r);break;case t.Info:console.info(r);break;case t.Warn:console.warn(r);break;case t.Error:console.error(r);break;default:throw new Error(`unknown log level ${n.level}`)}}))},e.debug=async function(e,n){await i(t.Debug,e,n)},e.error=async function(e,n){await i(t.Error,e,n)},e.info=async function(e,n){await i(t.Info,e,n)},e.trace=async function(e,n){await i(t.Trace,e,n)},e.warn=async function(e,n){await i(t.Warn,e,n)},e}({});Object.defineProperty(window.__TAURI__,"log",{value:__TAURI_PLUGIN_LOG__})}

@ -27,6 +27,6 @@
"tslib": "2.6.0" "tslib": "2.6.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -27,12 +27,30 @@ time = { version = "0.3", features = [ "serde", "parsing", "formatting" ] }
url = { version = "2", features = [ "serde" ] } url = { version = "2", features = [ "serde" ] }
serde_repr = "0.1" serde_repr = "0.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.5"
[target."cfg(windows)".dependencies] [target."cfg(windows)".dependencies]
win7-notifications = { version = "0.3.1", optional = true } win7-notifications = { version = "0.3.1", optional = true }
windows-version = { version = "0.1", optional = true } windows-version = { version = "0.1", optional = true }
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
lazy_static = { version = "1", optional = true }
image = { version = "0.24", 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.1" }
[dev-dependencies]
color-backtrace = "0.5"
ctor = "0.2"
maplit = "1.0"
[features] [features]
default = [ "zbus", "async" ]
async = []
windows7-compat = [ "win7-notifications", "windows-version" ] windows7-compat = [ "win7-notifications", "windows-version" ]

@ -4,7 +4,7 @@ Send message notifications (brief auto-expiring OS window element) to your user.
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -23,6 +23,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -160,7 +160,7 @@ mod imp {
deprecated = "This function does not work on Windows 7. Use `Self::notify` instead." deprecated = "This function does not work on Windows 7. Use `Self::notify` instead."
)] )]
pub fn show(self) -> crate::Result<()> { pub fn show(self) -> crate::Result<()> {
let mut notification = notify_rust::Notification::new(); let mut notification = crate::notify_rust::Notification::new();
if let Some(body) = self.body { if let Some(body) = self.body {
notification.body(&body); notification.body(&body);
} }
@ -186,7 +186,7 @@ mod imp {
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
let _ = notify_rust::set_application(if cfg!(feature = "custom-protocol") { let _ = crate::notify_rust::set_application(if cfg!(feature = "custom-protocol") {
&self.identifier &self.identifier
} else { } else {
"com.apple.Terminal" "com.apple.Terminal"

@ -32,6 +32,9 @@ mod commands;
mod error; mod error;
mod models; mod models;
#[allow(dead_code, unused_imports, deprecated, clippy::derivable_impls)]
mod notify_rust;
pub use error::{Error, Result}; pub use error::{Error, Result};
#[cfg(desktop)] #[cfg(desktop)]

@ -0,0 +1,161 @@
#![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)*);
}
};
}

@ -0,0 +1,245 @@
#![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 = super::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
}
}
}
}

@ -0,0 +1,17 @@
#![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";

@ -0,0 +1,159 @@
//! `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()
}

@ -0,0 +1,85 @@
#![cfg(all(test, unix, not(target_os = "macos")))]
use dbus::arg::messageitem::MessageItem as Item;
use ctor::ctor;
use super::*;
use self::Hint as Hint;
use super::Urgency::*;
#[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"));
}

@ -0,0 +1,229 @@
#[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()),
])
}
}

@ -0,0 +1,61 @@
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(&notification.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(&notification.body)
.maybe_subtitle(notification.subtitle.as_deref())
.maybe_sound(notification.sound_name.as_deref())
.delivery_date(delivery_date)
.send()?;
Ok(NotificationHandle::new(notification.clone()))
}

@ -0,0 +1,75 @@
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));
}
}

@ -0,0 +1,173 @@
//! 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,
}

@ -0,0 +1,480 @@
#[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 {
self.appname = appname.to_owned();
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 {
self.summary = summary.to_owned();
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 {
self.body = body.to_owned();
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 {
self.icon = icon.to_owned();
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 = super::xdg::NotificationBus::custom(sub_bus).ok_or("invalid subpath")?;
super::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,
}
}
}

@ -0,0 +1,238 @@
//! **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(&notification);
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"))
}

@ -0,0 +1,102 @@
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()))
}
}

@ -0,0 +1,74 @@
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,
}
}
}

@ -0,0 +1,40 @@
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 &notification.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 = &notification.app_id.as_ref().unwrap_or(powershell_app_id);
let mut toast = Toast::new(app_id)
.title(&notification.summary)
.text1(notification.subtitle.as_ref().map_or("", AsRef::as_ref)) // subtitle
.text2(&notification.body)
.sound(sound)
.duration(duration);
if let Some(image_path) = &notification.path_to_image {
toast = toast.image(Path::new(&image_path), "");
}
toast
.show()
.map_err(|e| Error::from(ErrorKind::Msg(format!("{:?}", e))))
}

@ -0,0 +1,68 @@
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
}
}

@ -0,0 +1,328 @@
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 &notification.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;
}
}
}
(..) => (),
}
}
}
}

@ -0,0 +1,544 @@
//! 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();
}
}

@ -0,0 +1,285 @@
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",
&(
&notification.appname,
id,
&notification.icon,
&notification.summary,
&notification.body,
&notification.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;
}
_ => {}
}
}
_ => {}
}
}
}
}

@ -4,7 +4,7 @@ Read information about the operating system.
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -23,6 +23,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -42,7 +42,7 @@
## \[2.0.0-alpha.3] ## \[2.0.0-alpha.3]
- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update MSRV to 1.70. - [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update MSRV to 1.75.
- [`5de23e7`](https://github.com/tauri-apps/plugins-workspace/commit/5de23e79f9880921b62e4b7a8819bc0dbc833216)([#649](https://github.com/tauri-apps/plugins-workspace/pull/649)) Update to tauri@alpha.15. - [`5de23e7`](https://github.com/tauri-apps/plugins-workspace/commit/5de23e79f9880921b62e4b7a8819bc0dbc833216)([#649](https://github.com/tauri-apps/plugins-workspace/pull/649)) Update to tauri@alpha.15.
### Dependencies ### Dependencies

@ -4,7 +4,7 @@ Save filesystem and asset scopes and restore them when the app is reopened.
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -6,7 +6,7 @@ This plugin is a port of [electron-positioner](https://github.com/jenslind/elect
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -4,7 +4,7 @@ This plugin provides APIs to access the current process. To spawn child processe
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -23,6 +23,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -4,7 +4,7 @@ Access the system shell. Allows you to spawn child processes and manage files an
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -23,6 +23,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -103,8 +103,8 @@ pub fn execute<R: Runtime>(
args: ExecuteArgs, args: ExecuteArgs,
on_event: Channel, on_event: Channel,
options: CommandOptions, options: CommandOptions,
command_scope: CommandScope<'_, crate::scope::ScopeAllowedCommand>, command_scope: CommandScope<crate::scope::ScopeAllowedCommand>,
global_scope: GlobalScope<'_, crate::scope::ScopeAllowedCommand>, global_scope: GlobalScope<crate::scope::ScopeAllowedCommand>,
) -> crate::Result<ChildId> { ) -> crate::Result<ChildId> {
let scope = crate::scope::ShellScope { let scope = crate::scope::ShellScope {
scopes: command_scope scopes: command_scope

@ -19,7 +19,7 @@
## \[2.0.0-alpha.3] ## \[2.0.0-alpha.3]
- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update MSRV to 1.70. - [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update MSRV to 1.75.
## \[2.0.0-alpha.2] ## \[2.0.0-alpha.2]

@ -32,4 +32,4 @@ features = [
] ]
[target."cfg(target_os = \"linux\")".dependencies] [target."cfg(target_os = \"linux\")".dependencies]
zbus = "3" zbus = "4"

@ -4,7 +4,7 @@ Ensure a single instance of your tauri app is running.
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -9,6 +9,6 @@
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "2.0.0-beta.0" "@tauri-apps/cli": "2.0.0-beta.3"
} }
} }

@ -3,4 +3,4 @@
/target/ /target/
WixTools WixTools
/capabilities/schemas /gen/schemas

@ -5,7 +5,7 @@ description = "A Tauri App"
authors = [ "You" ] authors = [ "You" ]
repository = "" repository = ""
edition = "2021" edition = "2021"
rust-version = "1.70" rust-version = "1.75"
[dependencies] [dependencies]
serde_json = { workspace = true } serde_json = { workspace = true }

@ -10,8 +10,8 @@ use tauri::{
AppHandle, Config, Manager, RunEvent, Runtime, AppHandle, Config, Manager, RunEvent, Runtime,
}; };
use zbus::{ use zbus::{
blocking::{Connection, ConnectionBuilder}, blocking::{Builder, Connection},
dbus_interface, interface,
}; };
struct ConnectionHandle(Connection); struct ConnectionHandle(Connection);
@ -21,7 +21,7 @@ struct SingleInstanceDBus<R: Runtime> {
app_handle: AppHandle<R>, app_handle: AppHandle<R>,
} }
#[dbus_interface(name = "org.SingleInstance.DBus")] #[interface(name = "org.SingleInstance.DBus")]
impl<R: Runtime> SingleInstanceDBus<R> { impl<R: Runtime> SingleInstanceDBus<R> {
fn execute_callback(&mut self, argv: Vec<String>, cwd: String) { fn execute_callback(&mut self, argv: Vec<String>, cwd: String) {
(self.callback)(&self.app_handle, argv, cwd); (self.callback)(&self.app_handle, argv, cwd);
@ -43,7 +43,7 @@ pub fn init<R: Runtime>(f: Box<SingleInstanceCallback<R>>) -> TauriPlugin<R> {
let dbus_name = format!("org.{id}.SingleInstance"); let dbus_name = format!("org.{id}.SingleInstance");
let dbus_path = format!("/org/{id}/SingleInstance"); let dbus_path = format!("/org/{id}/SingleInstance");
match ConnectionBuilder::session() match Builder::session()
.unwrap() .unwrap()
.name(dbus_name.as_str()) .name(dbus_name.as_str())
.unwrap() .unwrap()

@ -4,7 +4,7 @@ Interface with SQL databases through [sqlx](https://github.com/launchbadge/sqlx)
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -4,7 +4,7 @@ Simple, persistent key-value store.
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.0" "@tauri-apps/api": "2.0.0-beta.2"
} }
} }

@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(t){"use strict";function a(t,a=!1){return window.__TAURI_INTERNALS__.transformCallback(t,a)}async function e(t,a={},e){return window.__TAURI_INTERNALS__.invoke(t,a,e)}var n;async function r(t,n,r){const i="string"==typeof r?.target?{kind:"AnyLabel",label:r.target}:r?.target??{kind:"Any"};return e("plugin:event|listen",{event:t,target:i,handler:a(n)}).then((a=>async()=>async function(t,a){await e("plugin:event|unlisten",{event:t,eventId:a})}(t,a)))}"function"==typeof SuppressedError&&SuppressedError,function(t){t.WINDOW_RESIZED="tauri://resize",t.WINDOW_MOVED="tauri://move",t.WINDOW_CLOSE_REQUESTED="tauri://close-requested",t.WINDOW_DESTROYED="tauri://destroyed",t.WINDOW_FOCUS="tauri://focus",t.WINDOW_BLUR="tauri://blur",t.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",t.WINDOW_THEME_CHANGED="tauri://theme-changed",t.WEBVIEW_CREATED="tauri://webview-created",t.WEBVIEW_FILE_DROP="tauri://file-drop",t.WEBVIEW_FILE_DROP_HOVER="tauri://file-drop-hover",t.WEBVIEW_FILE_DROP_CANCELLED="tauri://file-drop-cancelled"}(n||(n={}));return t.Store=class{constructor(t){this.path=t}async set(t,a){return await e("plugin:store|set",{path:this.path,key:t,value:a})}async get(t){return await e("plugin:store|get",{path:this.path,key:t})}async has(t){return await e("plugin:store|has",{path:this.path,key:t})}async delete(t){return await e("plugin:store|delete",{path:this.path,key:t})}async clear(){return await e("plugin:store|clear",{path:this.path})}async reset(){return await e("plugin:store|reset",{path:this.path})}async keys(){return await e("plugin:store|keys",{path:this.path})}async values(){return await e("plugin:store|values",{path:this.path})}async entries(){return await e("plugin:store|entries",{path:this.path})}async length(){return await e("plugin:store|length",{path:this.path})}async load(){return await e("plugin:store|load",{path:this.path})}async save(){return await e("plugin:store|save",{path:this.path})}async onKeyChange(t,a){return await r("store://change",(e=>{e.payload.path===this.path&&e.payload.key===t&&a(e.payload.value)}))}async onChange(t){return await r("store://change",(a=>{a.payload.path===this.path&&t(a.payload.key,a.payload.value)}))}},t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(t){"use strict";function a(t,a=!1){return window.__TAURI_INTERNALS__.transformCallback(t,a)}async function e(t,a={},e){return window.__TAURI_INTERNALS__.invoke(t,a,e)}var n;async function r(t,n,r){const i="string"==typeof r?.target?{kind:"AnyLabel",label:r.target}:r?.target??{kind:"Any"};return e("plugin:event|listen",{event:t,target:i,handler:a(n)}).then((a=>async()=>async function(t,a){await e("plugin:event|unlisten",{event:t,eventId:a})}(t,a)))}"function"==typeof SuppressedError&&SuppressedError,function(t){t.WINDOW_RESIZED="tauri://resize",t.WINDOW_MOVED="tauri://move",t.WINDOW_CLOSE_REQUESTED="tauri://close-requested",t.WINDOW_DESTROYED="tauri://destroyed",t.WINDOW_FOCUS="tauri://focus",t.WINDOW_BLUR="tauri://blur",t.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",t.WINDOW_THEME_CHANGED="tauri://theme-changed",t.WEBVIEW_CREATED="tauri://webview-created",t.FILE_DROP="tauri://file-drop",t.FILE_DROP_HOVER="tauri://file-drop-hover",t.FILE_DROP_CANCELLED="tauri://file-drop-cancelled"}(n||(n={}));return t.Store=class{constructor(t){this.path=t}async set(t,a){return await e("plugin:store|set",{path:this.path,key:t,value:a})}async get(t){return await e("plugin:store|get",{path:this.path,key:t})}async has(t){return await e("plugin:store|has",{path:this.path,key:t})}async delete(t){return await e("plugin:store|delete",{path:this.path,key:t})}async clear(){return await e("plugin:store|clear",{path:this.path})}async reset(){return await e("plugin:store|reset",{path:this.path})}async keys(){return await e("plugin:store|keys",{path:this.path})}async values(){return await e("plugin:store|values",{path:this.path})}async entries(){return await e("plugin:store|entries",{path:this.path})}async length(){return await e("plugin:store|length",{path:this.path})}async load(){return await e("plugin:store|load",{path:this.path})}async save(){return await e("plugin:store|save",{path:this.path})}async onKeyChange(t,a){return await r("store://change",(e=>{e.payload.path===this.path&&e.payload.key===t&&a(e.payload.value)}))}async onChange(t){return await r("store://change",(a=>{a.payload.path===this.path&&t(a.payload.key,a.payload.value)}))}},t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})}

@ -4,7 +4,7 @@ Store secrets and keys using the [IOTA Stronghold](https://github.com/iotaledger
## Install ## Install
_This plugin requires a Rust version of at least **1.70**_ _This plugin requires a Rust version of at least **1.75**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save