diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml
deleted file mode 100644
index b5e8cfd4d..000000000
--- a/.github/workflows/claude-code-review.yml
+++ /dev/null
@@ -1,44 +0,0 @@
-name: Claude Code Review
-
-on:
- pull_request:
- types: [opened, synchronize, ready_for_review, reopened]
- # Optional: Only run on specific file changes
- # paths:
- # - "src/**/*.ts"
- # - "src/**/*.tsx"
- # - "src/**/*.js"
- # - "src/**/*.jsx"
-
-jobs:
- claude-review:
- # Optional: Filter by PR author
- # if: |
- # github.event.pull_request.user.login == 'external-contributor' ||
- # github.event.pull_request.user.login == 'new-developer' ||
- # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
-
- runs-on: ubuntu-latest
- permissions:
- contents: read
- pull-requests: read
- issues: read
- id-token: write
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- fetch-depth: 1
-
- - name: Run Claude Code Review
- id: claude-review
- uses: anthropics/claude-code-action@v1
- with:
- claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
- plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
- plugins: 'code-review@claude-code-plugins'
- prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
- # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
- # or https://code.claude.com/docs/en/cli-reference for available options
-
diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml
deleted file mode 100644
index d300267f1..000000000
--- a/.github/workflows/claude.yml
+++ /dev/null
@@ -1,50 +0,0 @@
-name: Claude Code
-
-on:
- issue_comment:
- types: [created]
- pull_request_review_comment:
- types: [created]
- issues:
- types: [opened, assigned]
- pull_request_review:
- types: [submitted]
-
-jobs:
- claude:
- if: |
- (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
- (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
- (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
- (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
- runs-on: ubuntu-latest
- permissions:
- contents: read
- pull-requests: read
- issues: read
- id-token: write
- actions: read # Required for Claude to read CI results on PRs
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- fetch-depth: 1
-
- - name: Run Claude Code
- id: claude
- uses: anthropics/claude-code-action@v1
- with:
- claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
-
- # This is an optional setting that allows Claude to read CI results on PRs
- additional_permissions: |
- actions: read
-
- # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
- # prompt: 'Update the pull request description to include a summary of changes.'
-
- # Optional: Add claude_args to customize behavior and configuration
- # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
- # or https://code.claude.com/docs/en/cli-reference for available options
- # claude_args: '--allowed-tools Bash(gh pr:*)'
-
diff --git a/v2/examples/panic-recovery-test/README.md b/v2/examples/panic-recovery-test/README.md
deleted file mode 100644
index c0a6a7e5a..000000000
--- a/v2/examples/panic-recovery-test/README.md
+++ /dev/null
@@ -1,76 +0,0 @@
-# Panic Recovery Test
-
-This example demonstrates the Linux signal handler issue (#3965) and verifies the fix using `runtime.ResetSignalHandlers()`.
-
-## The Problem
-
-On Linux, WebKit installs signal handlers without the `SA_ONSTACK` flag, which prevents Go from recovering panics caused by nil pointer dereferences (SIGSEGV). Without the fix, the application crashes with:
-
-```
-signal 11 received but handler not on signal stack
-fatal error: non-Go code set up signal handler without SA_ONSTACK flag
-```
-
-## The Solution
-
-Call `runtime.ResetSignalHandlers()` immediately before code that might panic:
-
-```go
-import "github.com/wailsapp/wails/v2/pkg/runtime"
-
-go func() {
- defer func() {
- if err := recover(); err != nil {
- log.Printf("Recovered: %v", err)
- }
- }()
- runtime.ResetSignalHandlers()
- // Code that might panic...
-}()
-```
-
-## How to Reproduce
-
-### Prerequisites
-
-- Linux with WebKit2GTK 4.1 installed
-- Go 1.21+
-- Wails CLI
-
-### Steps
-
-1. Build the example:
- ```bash
- cd v2/examples/panic-recovery-test
- wails build -tags webkit2_41
- ```
-
-2. Run the application:
- ```bash
- ./build/bin/panic-recovery-test
- ```
-
-3. Wait ~10 seconds (the app auto-calls `Greet` after 5s, then waits another 5s before the nil pointer dereference)
-
-### Expected Result (with fix)
-
-The panic is recovered and you see:
-```
-------------------------------"invalid memory address or nil pointer dereference"
-```
-
-The application continues running.
-
-### Without the fix
-
-Comment out the `runtime.ResetSignalHandlers()` call in `app.go` and rebuild. The application will crash with a fatal signal 11 error.
-
-## Files
-
-- `app.go` - Contains the `Greet` function that demonstrates panic recovery
-- `frontend/src/main.js` - Auto-calls `Greet` after 5 seconds to trigger the test
-
-## Related
-
-- Issue: https://github.com/wailsapp/wails/issues/3965
-- Original fix PR: https://github.com/wailsapp/wails/pull/2152
diff --git a/v2/examples/panic-recovery-test/app.go b/v2/examples/panic-recovery-test/app.go
deleted file mode 100644
index ceb46e8d5..000000000
--- a/v2/examples/panic-recovery-test/app.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package main
-
-import (
- "context"
- "fmt"
- "time"
-
- "github.com/wailsapp/wails/v2/pkg/runtime"
-)
-
-// App struct
-type App struct {
- ctx context.Context
-}
-
-// NewApp creates a new App application struct
-func NewApp() *App {
- return &App{}
-}
-
-// startup is called when the app starts. The context is saved
-// so we can call the runtime methods
-func (a *App) startup(ctx context.Context) {
- a.ctx = ctx
-}
-
-// Greet returns a greeting for the given name
-func (a *App) Greet(name string) string {
- go func() {
- defer func() {
- if err := recover(); err != nil {
- fmt.Printf("------------------------------%#v\n", err)
- }
- }()
- time.Sleep(5 * time.Second)
- // Fix signal handlers right before potential panic using the Wails runtime
- runtime.ResetSignalHandlers()
- // Nil pointer dereference - causes SIGSEGV
- var t *time.Time
- fmt.Println(t.Unix())
- }()
-
- return fmt.Sprintf("Hello %s, It's show time!", name)
-}
diff --git a/v2/examples/panic-recovery-test/frontend/index.html b/v2/examples/panic-recovery-test/frontend/index.html
deleted file mode 100644
index d7aa4e942..000000000
--- a/v2/examples/panic-recovery-test/frontend/index.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- panic-test
-
-
-
-
-
-
diff --git a/v2/examples/panic-recovery-test/frontend/package.json b/v2/examples/panic-recovery-test/frontend/package.json
deleted file mode 100644
index a1b6f8e1a..000000000
--- a/v2/examples/panic-recovery-test/frontend/package.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "name": "frontend",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "preview": "vite preview"
- },
- "devDependencies": {
- "vite": "^3.0.7"
- }
-}
\ No newline at end of file
diff --git a/v2/examples/panic-recovery-test/frontend/src/app.css b/v2/examples/panic-recovery-test/frontend/src/app.css
deleted file mode 100644
index 59d06f692..000000000
--- a/v2/examples/panic-recovery-test/frontend/src/app.css
+++ /dev/null
@@ -1,54 +0,0 @@
-#logo {
- display: block;
- width: 50%;
- height: 50%;
- margin: auto;
- padding: 10% 0 0;
- background-position: center;
- background-repeat: no-repeat;
- background-size: 100% 100%;
- background-origin: content-box;
-}
-
-.result {
- height: 20px;
- line-height: 20px;
- margin: 1.5rem auto;
-}
-
-.input-box .btn {
- width: 60px;
- height: 30px;
- line-height: 30px;
- border-radius: 3px;
- border: none;
- margin: 0 0 0 20px;
- padding: 0 8px;
- cursor: pointer;
-}
-
-.input-box .btn:hover {
- background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%);
- color: #333333;
-}
-
-.input-box .input {
- border: none;
- border-radius: 3px;
- outline: none;
- height: 30px;
- line-height: 30px;
- padding: 0 10px;
- background-color: rgba(240, 240, 240, 1);
- -webkit-font-smoothing: antialiased;
-}
-
-.input-box .input:hover {
- border: none;
- background-color: rgba(255, 255, 255, 1);
-}
-
-.input-box .input:focus {
- border: none;
- background-color: rgba(255, 255, 255, 1);
-}
\ No newline at end of file
diff --git a/v2/examples/panic-recovery-test/frontend/src/assets/fonts/OFL.txt b/v2/examples/panic-recovery-test/frontend/src/assets/fonts/OFL.txt
deleted file mode 100644
index 9cac04ce8..000000000
--- a/v2/examples/panic-recovery-test/frontend/src/assets/fonts/OFL.txt
+++ /dev/null
@@ -1,93 +0,0 @@
-Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com),
-
-This Font Software is licensed under the SIL Open Font License, Version 1.1.
-This license is copied below, and is also available with a FAQ at:
-http://scripts.sil.org/OFL
-
-
------------------------------------------------------------
-SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
------------------------------------------------------------
-
-PREAMBLE
-The goals of the Open Font License (OFL) are to stimulate worldwide
-development of collaborative font projects, to support the font creation
-efforts of academic and linguistic communities, and to provide a free and
-open framework in which fonts may be shared and improved in partnership
-with others.
-
-The OFL allows the licensed fonts to be used, studied, modified and
-redistributed freely as long as they are not sold by themselves. The
-fonts, including any derivative works, can be bundled, embedded,
-redistributed and/or sold with any software provided that any reserved
-names are not used by derivative works. The fonts and derivatives,
-however, cannot be released under any other type of license. The
-requirement for fonts to remain under this license does not apply
-to any document created using the fonts or their derivatives.
-
-DEFINITIONS
-"Font Software" refers to the set of files released by the Copyright
-Holder(s) under this license and clearly marked as such. This may
-include source files, build scripts and documentation.
-
-"Reserved Font Name" refers to any names specified as such after the
-copyright statement(s).
-
-"Original Version" refers to the collection of Font Software components as
-distributed by the Copyright Holder(s).
-
-"Modified Version" refers to any derivative made by adding to, deleting,
-or substituting -- in part or in whole -- any of the components of the
-Original Version, by changing formats or by porting the Font Software to a
-new environment.
-
-"Author" refers to any designer, engineer, programmer, technical
-writer or other person who contributed to the Font Software.
-
-PERMISSION & CONDITIONS
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of the Font Software, to use, study, copy, merge, embed, modify,
-redistribute, and sell modified and unmodified copies of the Font
-Software, subject to the following conditions:
-
-1) Neither the Font Software nor any of its individual components,
-in Original or Modified Versions, may be sold by itself.
-
-2) Original or Modified Versions of the Font Software may be bundled,
-redistributed and/or sold with any software, provided that each copy
-contains the above copyright notice and this license. These can be
-included either as stand-alone text files, human-readable headers or
-in the appropriate machine-readable metadata fields within text or
-binary files as long as those fields can be easily viewed by the user.
-
-3) No Modified Version of the Font Software may use the Reserved Font
-Name(s) unless explicit written permission is granted by the corresponding
-Copyright Holder. This restriction only applies to the primary font name as
-presented to the users.
-
-4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
-Software shall not be used to promote, endorse or advertise any
-Modified Version, except to acknowledge the contribution(s) of the
-Copyright Holder(s) and the Author(s) or with their explicit written
-permission.
-
-5) The Font Software, modified or unmodified, in part or in whole,
-must be distributed entirely under this license, and must not be
-distributed under any other license. The requirement for fonts to
-remain under this license does not apply to any document created
-using the Font Software.
-
-TERMINATION
-This license becomes null and void if any of the above conditions are
-not met.
-
-DISCLAIMER
-THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
-OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
-COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
-DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
-OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/v2/examples/panic-recovery-test/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 b/v2/examples/panic-recovery-test/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2
deleted file mode 100644
index 2f9cc5964..000000000
Binary files a/v2/examples/panic-recovery-test/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 and /dev/null differ
diff --git a/v2/examples/panic-recovery-test/frontend/src/assets/images/logo-universal.png b/v2/examples/panic-recovery-test/frontend/src/assets/images/logo-universal.png
deleted file mode 100644
index d63303bfa..000000000
Binary files a/v2/examples/panic-recovery-test/frontend/src/assets/images/logo-universal.png and /dev/null differ
diff --git a/v2/examples/panic-recovery-test/frontend/src/main.js b/v2/examples/panic-recovery-test/frontend/src/main.js
deleted file mode 100644
index ea5e74fc6..000000000
--- a/v2/examples/panic-recovery-test/frontend/src/main.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import './style.css';
-import './app.css';
-
-import logo from './assets/images/logo-universal.png';
-import {Greet} from '../wailsjs/go/main/App';
-
-document.querySelector('#app').innerHTML = `
-
-
Please enter your name below 👇
-
-
-
-
-
-`;
-document.getElementById('logo').src = logo;
-
-let nameElement = document.getElementById("name");
-nameElement.focus();
-let resultElement = document.getElementById("result");
-
-// Setup the greet function
-window.greet = function () {
- // Get name
- let name = nameElement.value;
-
- // Check if the input is empty
- if (name === "") return;
-
- // Call App.Greet(name)
- try {
- Greet(name)
- .then((result) => {
- // Update result with data back from App.Greet()
- resultElement.innerText = result;
- })
- .catch((err) => {
- console.error(err);
- });
- } catch (err) {
- console.error(err);
- }
-};
-
-// Auto-call Greet after 5 seconds to trigger the panic test
-setTimeout(() => {
- console.log("Auto-calling Greet to trigger panic test...");
- Greet("PanicTest")
- .then((result) => {
- resultElement.innerText = result + " (auto-called - panic will occur in 5s)";
- })
- .catch((err) => {
- console.error("Error:", err);
- });
-}, 5000);
diff --git a/v2/examples/panic-recovery-test/frontend/src/style.css b/v2/examples/panic-recovery-test/frontend/src/style.css
deleted file mode 100644
index 3940d6c63..000000000
--- a/v2/examples/panic-recovery-test/frontend/src/style.css
+++ /dev/null
@@ -1,26 +0,0 @@
-html {
- background-color: rgba(27, 38, 54, 1);
- text-align: center;
- color: white;
-}
-
-body {
- margin: 0;
- color: white;
- font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
- "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
- sans-serif;
-}
-
-@font-face {
- font-family: "Nunito";
- font-style: normal;
- font-weight: 400;
- src: local(""),
- url("assets/fonts/nunito-v16-latin-regular.woff2") format("woff2");
-}
-
-#app {
- height: 100vh;
- text-align: center;
-}
diff --git a/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.d.ts b/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.d.ts
deleted file mode 100755
index 02a3bb988..000000000
--- a/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.d.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
-// This file is automatically generated. DO NOT EDIT
-
-export function Greet(arg1:string):Promise;
diff --git a/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.js b/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.js
deleted file mode 100755
index c71ae77cb..000000000
--- a/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// @ts-check
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
-// This file is automatically generated. DO NOT EDIT
-
-export function Greet(arg1) {
- return window['go']['main']['App']['Greet'](arg1);
-}
diff --git a/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/package.json b/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/package.json
deleted file mode 100644
index 1e7c8a5d7..000000000
--- a/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/package.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "name": "@wailsapp/runtime",
- "version": "2.0.0",
- "description": "Wails Javascript runtime library",
- "main": "runtime.js",
- "types": "runtime.d.ts",
- "scripts": {
- },
- "repository": {
- "type": "git",
- "url": "git+https://github.com/wailsapp/wails.git"
- },
- "keywords": [
- "Wails",
- "Javascript",
- "Go"
- ],
- "author": "Lea Anthony ",
- "license": "MIT",
- "bugs": {
- "url": "https://github.com/wailsapp/wails/issues"
- },
- "homepage": "https://github.com/wailsapp/wails#readme"
-}
diff --git a/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.d.ts b/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.d.ts
deleted file mode 100644
index 4445dac21..000000000
--- a/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.d.ts
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- _ __ _ __
-| | / /___ _(_) /____
-| | /| / / __ `/ / / ___/
-| |/ |/ / /_/ / / (__ )
-|__/|__/\__,_/_/_/____/
-The electron alternative for Go
-(c) Lea Anthony 2019-present
-*/
-
-export interface Position {
- x: number;
- y: number;
-}
-
-export interface Size {
- w: number;
- h: number;
-}
-
-export interface Screen {
- isCurrent: boolean;
- isPrimary: boolean;
- width : number
- height : number
-}
-
-// Environment information such as platform, buildtype, ...
-export interface EnvironmentInfo {
- buildType: string;
- platform: string;
- arch: string;
-}
-
-// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
-// emits the given event. Optional data may be passed with the event.
-// This will trigger any event listeners.
-export function EventsEmit(eventName: string, ...data: any): void;
-
-// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
-export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
-
-// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
-// sets up a listener for the given event name, but will only trigger a given number times.
-export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
-
-// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
-// sets up a listener for the given event name, but will only trigger once.
-export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
-
-// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
-// unregisters the listener for the given event name.
-export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
-
-// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
-// unregisters all listeners.
-export function EventsOffAll(): void;
-
-// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
-// logs the given message as a raw message
-export function LogPrint(message: string): void;
-
-// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
-// logs the given message at the `trace` log level.
-export function LogTrace(message: string): void;
-
-// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
-// logs the given message at the `debug` log level.
-export function LogDebug(message: string): void;
-
-// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
-// logs the given message at the `error` log level.
-export function LogError(message: string): void;
-
-// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
-// logs the given message at the `fatal` log level.
-// The application will quit after calling this method.
-export function LogFatal(message: string): void;
-
-// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
-// logs the given message at the `info` log level.
-export function LogInfo(message: string): void;
-
-// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
-// logs the given message at the `warning` log level.
-export function LogWarning(message: string): void;
-
-// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
-// Forces a reload by the main application as well as connected browsers.
-export function WindowReload(): void;
-
-// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
-// Reloads the application frontend.
-export function WindowReloadApp(): void;
-
-// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
-// Sets the window AlwaysOnTop or not on top.
-export function WindowSetAlwaysOnTop(b: boolean): void;
-
-// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
-// *Windows only*
-// Sets window theme to system default (dark/light).
-export function WindowSetSystemDefaultTheme(): void;
-
-// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
-// *Windows only*
-// Sets window to light theme.
-export function WindowSetLightTheme(): void;
-
-// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
-// *Windows only*
-// Sets window to dark theme.
-export function WindowSetDarkTheme(): void;
-
-// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
-// Centers the window on the monitor the window is currently on.
-export function WindowCenter(): void;
-
-// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
-// Sets the text in the window title bar.
-export function WindowSetTitle(title: string): void;
-
-// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
-// Makes the window full screen.
-export function WindowFullscreen(): void;
-
-// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
-// Restores the previous window dimensions and position prior to full screen.
-export function WindowUnfullscreen(): void;
-
-// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
-// Returns the state of the window, i.e. whether the window is in full screen mode or not.
-export function WindowIsFullscreen(): Promise;
-
-// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
-// Sets the width and height of the window.
-export function WindowSetSize(width: number, height: number): void;
-
-// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
-// Gets the width and height of the window.
-export function WindowGetSize(): Promise;
-
-// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
-// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
-// Setting a size of 0,0 will disable this constraint.
-export function WindowSetMaxSize(width: number, height: number): void;
-
-// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
-// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
-// Setting a size of 0,0 will disable this constraint.
-export function WindowSetMinSize(width: number, height: number): void;
-
-// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
-// Sets the window position relative to the monitor the window is currently on.
-export function WindowSetPosition(x: number, y: number): void;
-
-// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
-// Gets the window position relative to the monitor the window is currently on.
-export function WindowGetPosition(): Promise;
-
-// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
-// Hides the window.
-export function WindowHide(): void;
-
-// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
-// Shows the window, if it is currently hidden.
-export function WindowShow(): void;
-
-// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
-// Maximises the window to fill the screen.
-export function WindowMaximise(): void;
-
-// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
-// Toggles between Maximised and UnMaximised.
-export function WindowToggleMaximise(): void;
-
-// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
-// Restores the window to the dimensions and position prior to maximising.
-export function WindowUnmaximise(): void;
-
-// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
-// Returns the state of the window, i.e. whether the window is maximised or not.
-export function WindowIsMaximised(): Promise;
-
-// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
-// Minimises the window.
-export function WindowMinimise(): void;
-
-// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
-// Restores the window to the dimensions and position prior to minimising.
-export function WindowUnminimise(): void;
-
-// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
-// Returns the state of the window, i.e. whether the window is minimised or not.
-export function WindowIsMinimised(): Promise;
-
-// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
-// Returns the state of the window, i.e. whether the window is normal or not.
-export function WindowIsNormal(): Promise;
-
-// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
-// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
-export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
-
-// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
-// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
-export function ScreenGetAll(): Promise;
-
-// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
-// Opens the given URL in the system browser.
-export function BrowserOpenURL(url: string): void;
-
-// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
-// Returns information about the environment
-export function Environment(): Promise;
-
-// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
-// Quits the application.
-export function Quit(): void;
-
-// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
-// Hides the application.
-export function Hide(): void;
-
-// [Show](https://wails.io/docs/reference/runtime/intro#show)
-// Shows the application.
-export function Show(): void;
-
-// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
-// Returns the current text stored on clipboard
-export function ClipboardGetText(): Promise;
-
-// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
-// Sets a text on the clipboard
-export function ClipboardSetText(text: string): Promise;
-
-// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
-// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
-export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
-
-// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
-// OnFileDropOff removes the drag and drop listeners and handlers.
-export function OnFileDropOff() :void
-
-// Check if the file path resolver is available
-export function CanResolveFilePaths(): boolean;
-
-// Resolves file paths for an array of files
-export function ResolveFilePaths(files: File[]): void
\ No newline at end of file
diff --git a/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.js b/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.js
deleted file mode 100644
index 7cb89d750..000000000
--- a/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.js
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- _ __ _ __
-| | / /___ _(_) /____
-| | /| / / __ `/ / / ___/
-| |/ |/ / /_/ / / (__ )
-|__/|__/\__,_/_/_/____/
-The electron alternative for Go
-(c) Lea Anthony 2019-present
-*/
-
-export function LogPrint(message) {
- window.runtime.LogPrint(message);
-}
-
-export function LogTrace(message) {
- window.runtime.LogTrace(message);
-}
-
-export function LogDebug(message) {
- window.runtime.LogDebug(message);
-}
-
-export function LogInfo(message) {
- window.runtime.LogInfo(message);
-}
-
-export function LogWarning(message) {
- window.runtime.LogWarning(message);
-}
-
-export function LogError(message) {
- window.runtime.LogError(message);
-}
-
-export function LogFatal(message) {
- window.runtime.LogFatal(message);
-}
-
-export function EventsOnMultiple(eventName, callback, maxCallbacks) {
- return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
-}
-
-export function EventsOn(eventName, callback) {
- return EventsOnMultiple(eventName, callback, -1);
-}
-
-export function EventsOff(eventName, ...additionalEventNames) {
- return window.runtime.EventsOff(eventName, ...additionalEventNames);
-}
-
-export function EventsOffAll() {
- return window.runtime.EventsOffAll();
-}
-
-export function EventsOnce(eventName, callback) {
- return EventsOnMultiple(eventName, callback, 1);
-}
-
-export function EventsEmit(eventName) {
- let args = [eventName].slice.call(arguments);
- return window.runtime.EventsEmit.apply(null, args);
-}
-
-export function WindowReload() {
- window.runtime.WindowReload();
-}
-
-export function WindowReloadApp() {
- window.runtime.WindowReloadApp();
-}
-
-export function WindowSetAlwaysOnTop(b) {
- window.runtime.WindowSetAlwaysOnTop(b);
-}
-
-export function WindowSetSystemDefaultTheme() {
- window.runtime.WindowSetSystemDefaultTheme();
-}
-
-export function WindowSetLightTheme() {
- window.runtime.WindowSetLightTheme();
-}
-
-export function WindowSetDarkTheme() {
- window.runtime.WindowSetDarkTheme();
-}
-
-export function WindowCenter() {
- window.runtime.WindowCenter();
-}
-
-export function WindowSetTitle(title) {
- window.runtime.WindowSetTitle(title);
-}
-
-export function WindowFullscreen() {
- window.runtime.WindowFullscreen();
-}
-
-export function WindowUnfullscreen() {
- window.runtime.WindowUnfullscreen();
-}
-
-export function WindowIsFullscreen() {
- return window.runtime.WindowIsFullscreen();
-}
-
-export function WindowGetSize() {
- return window.runtime.WindowGetSize();
-}
-
-export function WindowSetSize(width, height) {
- window.runtime.WindowSetSize(width, height);
-}
-
-export function WindowSetMaxSize(width, height) {
- window.runtime.WindowSetMaxSize(width, height);
-}
-
-export function WindowSetMinSize(width, height) {
- window.runtime.WindowSetMinSize(width, height);
-}
-
-export function WindowSetPosition(x, y) {
- window.runtime.WindowSetPosition(x, y);
-}
-
-export function WindowGetPosition() {
- return window.runtime.WindowGetPosition();
-}
-
-export function WindowHide() {
- window.runtime.WindowHide();
-}
-
-export function WindowShow() {
- window.runtime.WindowShow();
-}
-
-export function WindowMaximise() {
- window.runtime.WindowMaximise();
-}
-
-export function WindowToggleMaximise() {
- window.runtime.WindowToggleMaximise();
-}
-
-export function WindowUnmaximise() {
- window.runtime.WindowUnmaximise();
-}
-
-export function WindowIsMaximised() {
- return window.runtime.WindowIsMaximised();
-}
-
-export function WindowMinimise() {
- window.runtime.WindowMinimise();
-}
-
-export function WindowUnminimise() {
- window.runtime.WindowUnminimise();
-}
-
-export function WindowSetBackgroundColour(R, G, B, A) {
- window.runtime.WindowSetBackgroundColour(R, G, B, A);
-}
-
-export function ScreenGetAll() {
- return window.runtime.ScreenGetAll();
-}
-
-export function WindowIsMinimised() {
- return window.runtime.WindowIsMinimised();
-}
-
-export function WindowIsNormal() {
- return window.runtime.WindowIsNormal();
-}
-
-export function BrowserOpenURL(url) {
- window.runtime.BrowserOpenURL(url);
-}
-
-export function Environment() {
- return window.runtime.Environment();
-}
-
-export function Quit() {
- window.runtime.Quit();
-}
-
-export function Hide() {
- window.runtime.Hide();
-}
-
-export function Show() {
- window.runtime.Show();
-}
-
-export function ClipboardGetText() {
- return window.runtime.ClipboardGetText();
-}
-
-export function ClipboardSetText(text) {
- return window.runtime.ClipboardSetText(text);
-}
-
-/**
- * Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
- *
- * @export
- * @callback OnFileDropCallback
- * @param {number} x - x coordinate of the drop
- * @param {number} y - y coordinate of the drop
- * @param {string[]} paths - A list of file paths.
- */
-
-/**
- * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
- *
- * @export
- * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
- * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
- */
-export function OnFileDrop(callback, useDropTarget) {
- return window.runtime.OnFileDrop(callback, useDropTarget);
-}
-
-/**
- * OnFileDropOff removes the drag and drop listeners and handlers.
- */
-export function OnFileDropOff() {
- return window.runtime.OnFileDropOff();
-}
-
-export function CanResolveFilePaths() {
- return window.runtime.CanResolveFilePaths();
-}
-
-export function ResolveFilePaths(files) {
- return window.runtime.ResolveFilePaths(files);
-}
\ No newline at end of file
diff --git a/v2/examples/panic-recovery-test/go.mod b/v2/examples/panic-recovery-test/go.mod
deleted file mode 100644
index 026042cbf..000000000
--- a/v2/examples/panic-recovery-test/go.mod
+++ /dev/null
@@ -1,5 +0,0 @@
-module panic-recovery-test
-
-go 1.21
-
-require github.com/wailsapp/wails/v2 v2.11.0
diff --git a/v2/examples/panic-recovery-test/main.go b/v2/examples/panic-recovery-test/main.go
deleted file mode 100644
index f6a38e86c..000000000
--- a/v2/examples/panic-recovery-test/main.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package main
-
-import (
- "embed"
-
- "github.com/wailsapp/wails/v2"
- "github.com/wailsapp/wails/v2/pkg/options"
- "github.com/wailsapp/wails/v2/pkg/options/assetserver"
-)
-
-//go:embed all:frontend/dist
-var assets embed.FS
-
-func main() {
- // Create an instance of the app structure
- app := NewApp()
-
- // Create application with options
- err := wails.Run(&options.App{
- Title: "panic-test",
- Width: 1024,
- Height: 768,
- AssetServer: &assetserver.Options{
- Assets: assets,
- },
- BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
- OnStartup: app.startup,
- Bind: []interface{}{
- app,
- },
- })
-
- if err != nil {
- println("Error:", err.Error())
- }
-}
diff --git a/v2/examples/panic-recovery-test/wails.json b/v2/examples/panic-recovery-test/wails.json
deleted file mode 100644
index 56770f091..000000000
--- a/v2/examples/panic-recovery-test/wails.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "$schema": "https://wails.io/schemas/config.v2.json",
- "name": "panic-recovery-test",
- "outputfilename": "panic-recovery-test",
- "frontend:install": "npm install",
- "frontend:build": "npm run build",
- "frontend:dev:watcher": "npm run dev",
- "frontend:dev:serverUrl": "auto",
- "author": {
- "name": "Lea Anthony",
- "email": "lea.anthony@gmail.com"
- }
-}
diff --git a/v2/go.mod b/v2/go.mod
index f1287bde7..1a40badd2 100644
--- a/v2/go.mod
+++ b/v2/go.mod
@@ -17,7 +17,7 @@ require (
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/jackmordaunt/icns v1.0.0
- github.com/jaypipes/ghw v0.21.3
+ github.com/jaypipes/ghw v0.13.0
github.com/labstack/echo/v4 v4.13.3
github.com/labstack/gommon v0.4.2
github.com/leaanthony/clir v1.3.0
@@ -51,9 +51,9 @@ require (
atomicgo.dev/keyboard v0.2.9 // indirect
atomicgo.dev/schedule v0.1.0 // indirect
dario.cat/mergo v1.0.0 // indirect
- git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v1.1.5 // indirect
+ github.com/StackExchange/wmi v1.2.1 // indirect
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
@@ -72,7 +72,7 @@ require (
github.com/gorilla/css v1.0.1 // indirect
github.com/itchyny/gojq v0.12.13 // indirect
github.com/itchyny/timefmt-go v0.1.5 // indirect
- github.com/jaypipes/pcidb v1.1.1 // indirect
+ github.com/jaypipes/pcidb v1.0.1 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
@@ -82,6 +82,7 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
+ github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
@@ -100,7 +101,6 @@ require (
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yuin/goldmark v1.7.4 // indirect
github.com/yuin/goldmark-emoji v1.0.3 // indirect
- github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/image v0.12.0 // indirect
golang.org/x/sync v0.11.0 // indirect
@@ -108,6 +108,6 @@ require (
golang.org/x/text v0.22.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
- howett.net/plist v1.0.2-0.20250314012144-ee69052608d9 // indirect
+ howett.net/plist v1.0.0 // indirect
mvdan.cc/sh/v3 v3.7.0 // indirect
)
diff --git a/v2/go.sum b/v2/go.sum
index 2cfe9f7ab..53e56707e 100644
--- a/v2/go.sum
+++ b/v2/go.sum
@@ -8,8 +8,6 @@ atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs=
atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
-git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA=
-git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc=
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
@@ -26,6 +24,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
+github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
+github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
@@ -88,7 +88,7 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0=
github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A=
-github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
@@ -117,10 +117,10 @@ github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm
github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ=
github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo=
-github.com/jaypipes/ghw v0.21.3 h1:v5mUHM+RN854Vqmk49Uh213jyUA4+8uqaRajlYESsh8=
-github.com/jaypipes/ghw v0.21.3/go.mod h1:GPrvwbtPoxYUenr74+nAnWbardIZq600vJDD5HnPsPE=
-github.com/jaypipes/pcidb v1.1.1 h1:QmPhpsbmmnCwZmHeYAATxEaoRuiMAJusKYkUncMC0ro=
-github.com/jaypipes/pcidb v1.1.1/go.mod h1:x27LT2krrUgjf875KxQXKB0Ha/YXLdZRVmw6hH0G7g8=
+github.com/jaypipes/ghw v0.13.0 h1:log8MXuB8hzTNnSktqpXMHc0c/2k/WgjOMSUtnI1RV4=
+github.com/jaypipes/ghw v0.13.0/go.mod h1:In8SsaDqlb1oTyrbmTC14uy+fbBMvp+xdqX51MidlD8=
+github.com/jaypipes/pcidb v1.0.1 h1:WB2zh27T3nwg8AE8ei81sNRb9yWBii3JGNJtT7K9Oic=
+github.com/jaypipes/pcidb v1.0.1/go.mod h1:6xYUz/yYEyOkIkUt2t2J2folIuZ4Yg6uByCGFXMCeE4=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
@@ -178,6 +178,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
@@ -261,8 +263,6 @@ github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=
github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhbIQY4=
github.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
-github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
-github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@@ -340,6 +340,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@@ -347,7 +348,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-howett.net/plist v1.0.2-0.20250314012144-ee69052608d9 h1:eeH1AIcPvSc0Z25ThsYF+Xoqbn0CI/YnXVYoTLFdGQw=
-howett.net/plist v1.0.2-0.20250314012144-ee69052608d9/go.mod h1:fyFX5Hj5tP1Mpk8obqA9MZgXT416Q5711SDT7dQLTLk=
+howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
+howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg=
mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8=
diff --git a/v2/internal/frontend/desktop/darwin/Application.h b/v2/internal/frontend/desktop/darwin/Application.h
index c3cd8075a..4d8bbd37b 100644
--- a/v2/internal/frontend/desktop/darwin/Application.h
+++ b/v2/internal/frontend/desktop/darwin/Application.h
@@ -69,21 +69,6 @@ void UpdateMenuItem(void* nsmenuitem, int checked);
void RunMainLoop(void);
void ReleaseContext(void *inctx);
-/* Notifications */
-bool IsNotificationAvailable(void *inctx);
-bool CheckBundleIdentifier(void *inctx);
-bool EnsureDelegateInitialized(void *inctx);
-void RequestNotificationAuthorization(void *inctx, int channelID);
-void CheckNotificationAuthorization(void *inctx, int channelID);
-void SendNotification(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *data_json);
-void SendNotificationWithActions(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *categoryId, const char *actions_json);
-void RegisterNotificationCategory(void *inctx, int channelID, const char *categoryId, const char *actions_json, bool hasReplyField, const char *replyPlaceholder, const char *replyButtonTitle);
-void RemoveNotificationCategory(void *inctx, int channelID, const char *categoryId);
-void RemoveAllPendingNotifications(void *inctx);
-void RemovePendingNotification(void *inctx, const char *identifier);
-void RemoveAllDeliveredNotifications(void *inctx);
-void RemoveDeliveredNotification(void *inctx, const char *identifier);
-
NSString* safeInit(const char* input);
#endif /* Application_h */
diff --git a/v2/internal/frontend/desktop/darwin/Application.m b/v2/internal/frontend/desktop/darwin/Application.m
index 38b2f35ef..38d349c2c 100644
--- a/v2/internal/frontend/desktop/darwin/Application.m
+++ b/v2/internal/frontend/desktop/darwin/Application.m
@@ -367,74 +367,6 @@ void AppendSeparator(void* inMenu) {
}
-bool IsNotificationAvailable(void *inctx) {
- WailsContext *ctx = (__bridge WailsContext*)inctx;
- return [ctx IsNotificationAvailable];
-}
-
-bool CheckBundleIdentifier(void *inctx) {
- WailsContext *ctx = (__bridge WailsContext*)inctx;
- return [ctx CheckBundleIdentifier];
-}
-
-bool EnsureDelegateInitialized(void *inctx) {
- WailsContext *ctx = (__bridge WailsContext*)inctx;
- return [ctx EnsureDelegateInitialized];
-}
-
-void RequestNotificationAuthorization(void *inctx, int channelID) {
- WailsContext *ctx = (__bridge WailsContext*)inctx;
- [ctx RequestNotificationAuthorization:channelID];
-}
-
-void CheckNotificationAuthorization(void *inctx, int channelID) {
- WailsContext *ctx = (__bridge WailsContext*)inctx;
- [ctx CheckNotificationAuthorization:channelID];
-}
-
-void SendNotification(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *data_json) {
- WailsContext *ctx = (__bridge WailsContext*)inctx;
- [ctx SendNotification:channelID :identifier :title :subtitle :body :data_json];
-}
-
-void SendNotificationWithActions(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *categoryId, const char *actions_json) {
- WailsContext *ctx = (__bridge WailsContext*)inctx;
-
- [ctx SendNotificationWithActions:channelID :identifier :title :subtitle :body :categoryId :actions_json];
-}
-
-void RegisterNotificationCategory(void *inctx, int channelID, const char *categoryId, const char *actions_json, bool hasReplyField, const char *replyPlaceholder, const char *replyButtonTitle) {
- WailsContext *ctx = (__bridge WailsContext*)inctx;
-
- [ctx RegisterNotificationCategory:channelID :categoryId :actions_json :hasReplyField :replyPlaceholder :replyButtonTitle];
-}
-
-void RemoveNotificationCategory(void *inctx, int channelID, const char *categoryId) {
- WailsContext *ctx = (__bridge WailsContext*)inctx;
-
- [ctx RemoveNotificationCategory:channelID :categoryId];
-}
-
-void RemoveAllPendingNotifications(void *inctx) {
- WailsContext *ctx = (__bridge WailsContext*)inctx;
- [ctx RemoveAllPendingNotifications];
-}
-
-void RemovePendingNotification(void *inctx, const char *identifier) {
- WailsContext *ctx = (__bridge WailsContext*)inctx;
- [ctx RemovePendingNotification:identifier];
-}
-
-void RemoveAllDeliveredNotifications(void *inctx) {
- WailsContext *ctx = (__bridge WailsContext*)inctx;
- [ctx RemoveAllDeliveredNotifications];
-}
-
-void RemoveDeliveredNotification(void *inctx, const char *identifier) {
- WailsContext *ctx = (__bridge WailsContext*)inctx;
- [ctx RemoveDeliveredNotification:identifier];
-}
-
void Run(void *inctx, const char* url) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
diff --git a/v2/internal/frontend/desktop/darwin/WailsContext.h b/v2/internal/frontend/desktop/darwin/WailsContext.h
index aafc3a1d4..2ec6d8707 100644
--- a/v2/internal/frontend/desktop/darwin/WailsContext.h
+++ b/v2/internal/frontend/desktop/darwin/WailsContext.h
@@ -92,24 +92,10 @@ struct Preferences {
- (void) ShowApplication;
- (void) Quit;
-- (void) MessageDialog :(NSString*)dialogType :(NSString*)title :(NSString*)message :(NSString*)button1 :(NSString*)button2 :(NSString*)button3 :(NSString*)button4 :(NSString*)defaultButton :(NSString*)cancelButton :(void*)iconData :(int)iconDataLength;
+-(void) MessageDialog :(NSString*)dialogType :(NSString*)title :(NSString*)message :(NSString*)button1 :(NSString*)button2 :(NSString*)button3 :(NSString*)button4 :(NSString*)defaultButton :(NSString*)cancelButton :(void*)iconData :(int)iconDataLength;
- (void) OpenFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)allowDirectories :(bool)allowFiles :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)resolveAliases :(bool)showHiddenFiles :(bool)allowMultipleSelection :(NSString*)filters;
- (void) SaveFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)showHiddenFiles :(NSString*)filters;
-- (bool) IsNotificationAvailable;
-- (bool) CheckBundleIdentifier;
-- (bool) EnsureDelegateInitialized;
-- (void) RequestNotificationAuthorization:(int)channelID;
-- (void) CheckNotificationAuthorization:(int)channelID;
-- (void) SendNotification:(int)channelID :(const char *)identifier :(const char *)title :(const char *)subtitle :(const char *)body :(const char *)dataJSON;
-- (void) SendNotificationWithActions:(int)channelID :(const char *)identifier :(const char *)title :(const char *)subtitle :(const char *)body :(const char *)categoryId :(const char *)actionsJSON;
-- (void) RegisterNotificationCategory:(int)channelID :(const char *)categoryId :(const char *)actionsJSON :(bool)hasReplyField :(const char *)replyPlaceholder :(const char *)replyButtonTitle;
-- (void) RemoveNotificationCategory:(int)channelID :(const char *)categoryId;
-- (void) RemoveAllPendingNotifications;
-- (void) RemovePendingNotification:(const char *)identifier;
-- (void) RemoveAllDeliveredNotifications;
-- (void) RemoveDeliveredNotification:(const char *)identifier;
-
- (void) loadRequest:(NSString*)url;
- (void) ExecJS:(NSString*)script;
- (NSScreen*) getCurrentScreen;
diff --git a/v2/internal/frontend/desktop/darwin/WailsContext.m b/v2/internal/frontend/desktop/darwin/WailsContext.m
index 51993eda2..7c9660d54 100644
--- a/v2/internal/frontend/desktop/darwin/WailsContext.m
+++ b/v2/internal/frontend/desktop/darwin/WailsContext.m
@@ -5,7 +5,6 @@
// Created by Lea Anthony on 10/10/21.
//
-#include "Application.h"
#import
#import
#import "WailsContext.h"
@@ -37,14 +36,6 @@ typedef void (^schemeTaskCaller)(id);
@end
-// Notifications
-#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
-#import
-#endif
-
-extern void captureResult(int channelID, bool success, const char* error);
-extern void didReceiveNotificationResponse(const char *jsonPayload, const char* error);
-
@implementation WailsContext
- (void) SetSize:(int)width :(int)height {
@@ -732,357 +723,6 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char*
}
-/***** Notifications ******/
-- (bool) IsNotificationAvailable {
- if (@available(macOS 10.14, *)) {
- return YES;
- } else {
- return NO;
- }
-}
-
-- (bool) CheckBundleIdentifier {
- NSBundle *main = [NSBundle mainBundle];
- if (main.bundleIdentifier == nil) {
- return NO;
- }
- return YES;
-}
-
-- (void)userNotificationCenter:(UNUserNotificationCenter *)center
- willPresentNotification:(UNNotification *)notification
- withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler API_AVAILABLE(macos(10.14)) {
- UNNotificationPresentationOptions options = UNNotificationPresentationOptionSound;
-
- if (@available(macOS 11.0, *)) {
- // These options are only available in macOS 11.0+
- options = UNNotificationPresentationOptionList |
- UNNotificationPresentationOptionBanner |
- UNNotificationPresentationOptionSound;
- }
-
- completionHandler(options);
-}
-
-- (void)userNotificationCenter:(UNUserNotificationCenter *)center
-didReceiveNotificationResponse:(UNNotificationResponse *)response
- withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(macos(10.14)) {
-
- NSMutableDictionary *payload = [NSMutableDictionary dictionary];
-
- [payload setObject:response.notification.request.identifier forKey:@"id"];
- [payload setObject:response.actionIdentifier forKey:@"actionIdentifier"];
- [payload setObject:response.notification.request.content.title ?: @"" forKey:@"title"];
- [payload setObject:response.notification.request.content.body ?: @"" forKey:@"body"];
-
- if (response.notification.request.content.categoryIdentifier) {
- [payload setObject:response.notification.request.content.categoryIdentifier forKey:@"categoryId"];
- }
-
- if (response.notification.request.content.subtitle) {
- [payload setObject:response.notification.request.content.subtitle forKey:@"subtitle"];
- }
-
- if (response.notification.request.content.userInfo) {
- [payload setObject:response.notification.request.content.userInfo forKey:@"userInfo"];
- }
-
- if ([response isKindOfClass:[UNTextInputNotificationResponse class]]) {
- UNTextInputNotificationResponse *textResponse = (UNTextInputNotificationResponse *)response;
- [payload setObject:textResponse.userText forKey:@"userText"];
- }
-
- NSError *error = nil;
- NSData *jsonData = [NSJSONSerialization dataWithJSONObject:payload options:0 error:&error];
- if (error) {
- NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]];
- didReceiveNotificationResponse(NULL, [errorMsg UTF8String]);
- } else {
- NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
- didReceiveNotificationResponse([jsonString UTF8String], NULL);
- }
-
- completionHandler();
-}
-
-- (bool) EnsureDelegateInitialized {
- if (@available(macOS 10.14, *)) {
- UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
- center.delegate = (id)self;
- return YES;
- }
- return NO;
-}
-
-- (void) RequestNotificationAuthorization :(int)channelID {
- if (@available(macOS 10.14, *)) {
- if (![self EnsureDelegateInitialized]) {
- NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service.";
- captureResult(channelID, false, [errorMsg UTF8String]);
- return;
- }
-
- UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
- UNAuthorizationOptions options = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
-
- [center requestAuthorizationWithOptions:options completionHandler:^(BOOL granted, NSError * _Nullable error) {
- if (error) {
- NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]];
- captureResult(channelID, false, [errorMsg UTF8String]);
- } else {
- captureResult(channelID, granted, NULL);
- }
- }];
- } else {
- captureResult(channelID, false, "Notifications not available on macOS versions prior to 10.14");
- }
-}
-
-- (void) CheckNotificationAuthorization :(int) channelID {
- if (@available(macOS 10.14, *)) {
- if (![self EnsureDelegateInitialized]) {
- NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service.";
- captureResult(channelID, false, [errorMsg UTF8String]);
- return;
- }
-
- UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
- [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) {
- BOOL isAuthorized = (settings.authorizationStatus == UNAuthorizationStatusAuthorized);
- captureResult(channelID, isAuthorized, NULL);
- }];
- } else {
- captureResult(channelID, false, "Notifications not available on macOS versions prior to 10.14");
- }
-}
-
-- (UNMutableNotificationContent *)createNotificationContent:(const char *)title subtitle:(const char *)subtitle body:(const char *)body dataJSON:(const char *)dataJSON error:(NSError **)contentError API_AVAILABLE(macos(10.14)) {
- if (title == NULL) title = "";
- if (body == NULL) body = "";
-
- NSString *nsTitle = [NSString stringWithUTF8String:title];
- NSString *nsSubtitle = subtitle ? [NSString stringWithUTF8String:subtitle] : @"";
- NSString *nsBody = [NSString stringWithUTF8String:body];
-
- UNMutableNotificationContent *content = [[[UNMutableNotificationContent alloc] init] autorelease];
- content.title = nsTitle;
- if (![nsSubtitle isEqualToString:@""]) {
- content.subtitle = nsSubtitle;
- }
- content.body = nsBody;
- content.sound = [UNNotificationSound defaultSound];
-
- // Parse JSON data if provided
- if (dataJSON) {
- NSString *dataJsonStr = [NSString stringWithUTF8String:dataJSON];
- NSData *jsonData = [dataJsonStr dataUsingEncoding:NSUTF8StringEncoding];
- NSError *error = nil;
- NSDictionary *parsedData = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
- if (!error && parsedData) {
- content.userInfo = parsedData;
- } else if (error) {
- if (contentError) *contentError = error;
- }
- }
-
- return content;
-}
-
-- (void) sendNotificationWithRequest:(UNNotificationRequest *)request channelID:(int)channelID API_AVAILABLE(macos(10.14)) {
- UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
- [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
- if (error) {
- NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]];
- captureResult(channelID, false, [errorMsg UTF8String]);
- } else {
- captureResult(channelID, true, NULL);
- }
- }];
-}
-
-- (void) SendNotification:(int)channelID :(const char *)identifier :(const char *)title :(const char *)subtitle :(const char *)body :(const char *)dataJSON API_AVAILABLE(macos(10.14)) {
- if (![self EnsureDelegateInitialized]) {
- NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service.";
- captureResult(channelID, false, [errorMsg UTF8String]);
- return;
- }
-
- NSString *nsIdentifier = [NSString stringWithUTF8String:identifier];
-
- NSError *contentError = nil;
- UNMutableNotificationContent *content = [self createNotificationContent:title subtitle:subtitle body:body dataJSON:dataJSON error:&contentError];
- if (contentError) {
- NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [contentError localizedDescription]];
- captureResult(channelID, false, [errorMsg UTF8String]);
- return;
- }
-
- UNTimeIntervalNotificationTrigger *trigger = nil;
- UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:nsIdentifier content:content trigger:trigger];
-
- [self sendNotificationWithRequest:request channelID:channelID];
-}
-
-- (void) SendNotificationWithActions:(int)channelID :(const char *)identifier :(const char *)title :(const char *)subtitle :(const char *)body :(const char *)categoryId :(const char *)dataJSON API_AVAILABLE(macos(10.14)) {
- if (![self EnsureDelegateInitialized]) {
- NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service.";
- captureResult(channelID, false, [errorMsg UTF8String]);
- return;
- }
-
- NSString *nsIdentifier = [NSString stringWithUTF8String:identifier];
- NSString *nsCategoryId = [NSString stringWithUTF8String:categoryId];
-
- NSError *contentError = nil;
- UNMutableNotificationContent *content = [self createNotificationContent:title subtitle:subtitle body:body dataJSON:dataJSON error:&contentError];
- if (contentError) {
- NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [contentError localizedDescription]];
- captureResult(channelID, false, [errorMsg UTF8String]);
- return;
- }
-
- content.categoryIdentifier = nsCategoryId;
-
- UNTimeIntervalNotificationTrigger *trigger = nil;
- UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:nsIdentifier content:content trigger:trigger];
-
- [self sendNotificationWithRequest:request channelID:channelID];
-}
-
-- (void) RegisterNotificationCategory:(int)channelID :(const char *)categoryId :(const char *)actionsJSON :(bool)hasReplyField :(const char *)replyPlaceholder :(const char *)replyButtonTitle API_AVAILABLE(macos(10.14)) {
- if (![self EnsureDelegateInitialized]) {
- NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service.";
- captureResult(channelID, false, [errorMsg UTF8String]);
- return;
- }
-
- NSString *nsCategoryId = [NSString stringWithUTF8String:categoryId];
- NSString *actionsJsonStr = actionsJSON ? [NSString stringWithUTF8String:actionsJSON] : @"[]";
-
- NSData *jsonData = [actionsJsonStr dataUsingEncoding:NSUTF8StringEncoding];
- NSError *error = nil;
- NSArray *actionsArray = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
-
- if (error) {
- NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]];
- captureResult(channelID, false, [errorMsg UTF8String]);
- return;
- }
-
- NSMutableArray *actions = [NSMutableArray array];
- for (NSDictionary *actionDict in actionsArray) {
- NSString *actionId = actionDict[@"id"];
- NSString *actionTitle = actionDict[@"title"];
- BOOL destructive = [actionDict[@"destructive"] boolValue];
-
- if (actionId && actionTitle) {
- UNNotificationActionOptions options = UNNotificationActionOptionNone;
- if (destructive) options |= UNNotificationActionOptionDestructive;
-
- UNNotificationAction *action = [UNNotificationAction actionWithIdentifier:actionId
- title:actionTitle
- options:options];
- [actions addObject:action];
- }
- }
-
- if (hasReplyField) {
- // Defensive NULL checks: if hasReplyField is true, both strings must be non-NULL
- if (!replyPlaceholder || !replyButtonTitle) {
- NSString *errorMsg = @"hasReplyField is true but replyPlaceholder or replyButtonTitle is NULL";
- captureResult(channelID, false, [errorMsg UTF8String]);
- return;
- }
- NSString *placeholder = [NSString stringWithUTF8String:replyPlaceholder];
- NSString *buttonTitle = [NSString stringWithUTF8String:replyButtonTitle];
- UNTextInputNotificationAction *textAction =
- [UNTextInputNotificationAction actionWithIdentifier:@"TEXT_REPLY"
- title:buttonTitle
- options:UNNotificationActionOptionNone
- textInputButtonTitle:buttonTitle
- textInputPlaceholder:placeholder];
- [actions addObject:textAction];
- }
-
- UNNotificationCategory *newCategory = [UNNotificationCategory categoryWithIdentifier:nsCategoryId
- actions:actions
- intentIdentifiers:@[]
- options:UNNotificationCategoryOptionNone];
-
- UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
- [center getNotificationCategoriesWithCompletionHandler:^(NSSet *categories) {
- NSMutableSet *updatedCategories = [NSMutableSet setWithSet:categories];
-
- // Remove existing category with same identifier if found
- UNNotificationCategory *existingCategory = nil;
- for (UNNotificationCategory *category in updatedCategories) {
- if ([category.identifier isEqualToString:nsCategoryId]) {
- existingCategory = category;
- break;
- }
- }
- if (existingCategory) {
- [updatedCategories removeObject:existingCategory];
- }
-
- // Add the new category
- [updatedCategories addObject:newCategory];
- [center setNotificationCategories:updatedCategories];
-
- captureResult(channelID, true, NULL);
- }];
-}
-
-- (void) RemoveNotificationCategory:(int)channelID :(const char *)categoryId API_AVAILABLE(macos(10.14)) {
- NSString *nsCategoryId = [NSString stringWithUTF8String:categoryId];
- UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
-
- [center getNotificationCategoriesWithCompletionHandler:^(NSSet *categories) {
- NSMutableSet *updatedCategories = [NSMutableSet setWithSet:categories];
-
- // Find and remove the matching category
- UNNotificationCategory *categoryToRemove = nil;
- for (UNNotificationCategory *category in updatedCategories) {
- if ([category.identifier isEqualToString:nsCategoryId]) {
- categoryToRemove = category;
- break;
- }
- }
-
- if (categoryToRemove) {
- [updatedCategories removeObject:categoryToRemove];
- [center setNotificationCategories:updatedCategories];
- captureResult(channelID, true, NULL);
- } else {
- NSString *errorMsg = [NSString stringWithFormat:@"Category '%@' not found", nsCategoryId];
- captureResult(channelID, false, [errorMsg UTF8String]);
- }
- }];
-}
-
-- (void) RemoveAllPendingNotifications API_AVAILABLE(macos(10.14)) {
- UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
- [center removeAllPendingNotificationRequests];
-}
-
-- (void) RemovePendingNotification:(const char *)identifier API_AVAILABLE(macos(10.14)) {
- NSString *nsIdentifier = [NSString stringWithUTF8String:identifier];
- UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
- [center removePendingNotificationRequestsWithIdentifiers:@[nsIdentifier]];
-}
-
-- (void) RemoveAllDeliveredNotifications API_AVAILABLE(macos(10.14)) {
- UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
- [center removeAllDeliveredNotifications];
-}
-
-- (void) RemoveDeliveredNotification:(const char *)identifier API_AVAILABLE(macos(10.14)) {
- NSString *nsIdentifier = [NSString stringWithUTF8String:identifier];
- UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
- [center removeDeliveredNotificationsWithIdentifiers:@[nsIdentifier]];
-}
-
-
- (void) SetAbout :(NSString*)title :(NSString*)description :(void*)imagedata :(int)datalen {
self.aboutTitle = title;
self.aboutDescription = description;
@@ -1091,7 +731,7 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
self.aboutImage = [[NSImage alloc] initWithData:imageData];
}
-- (void) About {
+-(void) About {
WailsAlert *alert = [WailsAlert new];
[alert setAlertStyle:NSAlertStyleInformational];
diff --git a/v2/internal/frontend/desktop/darwin/notifications.go b/v2/internal/frontend/desktop/darwin/notifications.go
deleted file mode 100644
index b788841e0..000000000
--- a/v2/internal/frontend/desktop/darwin/notifications.go
+++ /dev/null
@@ -1,465 +0,0 @@
-//go:build darwin
-// +build darwin
-
-package darwin
-
-/*
-#cgo CFLAGS:-x objective-c
-#cgo LDFLAGS: -framework Foundation -framework Cocoa
-
-#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
-#cgo LDFLAGS: -framework UserNotifications
-#endif
-
-#import "Application.h"
-#import "WailsContext.h"
-*/
-import "C"
-import (
- "context"
- "encoding/json"
- "fmt"
- "os"
- "sync"
- "time"
- "unsafe"
-
- "github.com/wailsapp/wails/v2/internal/frontend"
-)
-
-// Package-scoped variable only accessible within this file
-var (
- currentFrontend *Frontend
- frontendMutex sync.RWMutex
- // Notification channels
- channels map[int]chan notificationChannel
- channelsLock sync.Mutex
- nextChannelID int
-
- notificationResultCallback func(result frontend.NotificationResult)
- callbackLock sync.RWMutex
-)
-
-const DefaultActionIdentifier = "DEFAULT_ACTION"
-const AppleDefaultActionIdentifier = "com.apple.UNNotificationDefaultActionIdentifier"
-
-// setCurrentFrontend sets the current frontend instance
-// This is called when RequestNotificationAuthorization or CheckNotificationAuthorization is called
-func setCurrentFrontend(f *Frontend) {
- frontendMutex.Lock()
- defer frontendMutex.Unlock()
- currentFrontend = f
-}
-
-// getCurrentFrontend gets the current frontend instance
-func getCurrentFrontend() *Frontend {
- frontendMutex.RLock()
- defer frontendMutex.RUnlock()
- return currentFrontend
-}
-
-type notificationChannel struct {
- Success bool
- Error error
-}
-
-func (f *Frontend) InitializeNotifications() error {
- if !f.IsNotificationAvailable() {
- return fmt.Errorf("notifications are not available on this system")
- }
- if !f.checkBundleIdentifier() {
- return fmt.Errorf("notifications require a valid bundle identifier")
- }
- if !bool(C.EnsureDelegateInitialized(f.mainWindow.context)) {
- return fmt.Errorf("failed to initialize notification center delegate")
- }
-
- channels = make(map[int]chan notificationChannel)
- nextChannelID = 0
-
- setCurrentFrontend(f)
-
- return nil
-}
-
-// CleanupNotifications is a macOS stub that does nothing.
-// (Linux-specific cleanup)
-func (f *Frontend) CleanupNotifications() {
- // No cleanup needed on macOS
-}
-
-func (f *Frontend) IsNotificationAvailable() bool {
- return bool(C.IsNotificationAvailable(f.mainWindow.context))
-}
-
-func (f *Frontend) checkBundleIdentifier() bool {
- return bool(C.CheckBundleIdentifier(f.mainWindow.context))
-}
-
-func (f *Frontend) RequestNotificationAuthorization() (bool, error) {
- ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
- defer cancel()
-
- id, resultCh := f.registerChannel()
-
- C.RequestNotificationAuthorization(f.mainWindow.context, C.int(id))
-
- select {
- case result := <-resultCh:
- close(resultCh)
- return result.Success, result.Error
- case <-ctx.Done():
- f.cleanupChannel(id)
- return false, fmt.Errorf("notification authorization timed out after 3 minutes: %w", ctx.Err())
- }
-}
-
-func (f *Frontend) CheckNotificationAuthorization() (bool, error) {
- ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
- defer cancel()
-
- id, resultCh := f.registerChannel()
-
- C.CheckNotificationAuthorization(f.mainWindow.context, C.int(id))
-
- select {
- case result := <-resultCh:
- close(resultCh)
- return result.Success, result.Error
- case <-ctx.Done():
- f.cleanupChannel(id)
- return false, fmt.Errorf("notification authorization timed out after 15s: %w", ctx.Err())
- }
-}
-
-// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body.
-func (f *Frontend) SendNotification(options frontend.NotificationOptions) error {
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
-
- cIdentifier := C.CString(options.ID)
- cTitle := C.CString(options.Title)
- cSubtitle := C.CString(options.Subtitle)
- cBody := C.CString(options.Body)
- defer C.free(unsafe.Pointer(cIdentifier))
- defer C.free(unsafe.Pointer(cTitle))
- defer C.free(unsafe.Pointer(cSubtitle))
- defer C.free(unsafe.Pointer(cBody))
-
- var cDataJSON *C.char
- if options.Data != nil {
- jsonData, err := json.Marshal(options.Data)
- if err != nil {
- return fmt.Errorf("failed to marshal notification data: %w", err)
- }
- cDataJSON = C.CString(string(jsonData))
- defer C.free(unsafe.Pointer(cDataJSON))
- }
-
- id, resultCh := f.registerChannel()
- C.SendNotification(f.mainWindow.context, C.int(id), cIdentifier, cTitle, cSubtitle, cBody, cDataJSON)
-
- select {
- case result := <-resultCh:
- close(resultCh)
- if !result.Success {
- if result.Error != nil {
- return result.Error
- }
- return fmt.Errorf("sending notification failed")
- }
- return nil
- case <-ctx.Done():
- f.cleanupChannel(id)
- return fmt.Errorf("sending notification timed out: %w", ctx.Err())
- }
-}
-
-// SendNotificationWithActions sends a notification with additional actions and inputs.
-// A NotificationCategory must be registered with RegisterNotificationCategory first. The `CategoryID` must match the registered category.
-// If a NotificationCategory is not registered a basic notification will be sent.
-func (f *Frontend) SendNotificationWithActions(options frontend.NotificationOptions) error {
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
-
- cIdentifier := C.CString(options.ID)
- cTitle := C.CString(options.Title)
- cSubtitle := C.CString(options.Subtitle)
- cBody := C.CString(options.Body)
- cCategoryID := C.CString(options.CategoryID)
- defer C.free(unsafe.Pointer(cIdentifier))
- defer C.free(unsafe.Pointer(cTitle))
- defer C.free(unsafe.Pointer(cSubtitle))
- defer C.free(unsafe.Pointer(cBody))
- defer C.free(unsafe.Pointer(cCategoryID))
-
- var cDataJSON *C.char
- if options.Data != nil {
- jsonData, err := json.Marshal(options.Data)
- if err != nil {
- return fmt.Errorf("failed to marshal notification data: %w", err)
- }
- cDataJSON = C.CString(string(jsonData))
- defer C.free(unsafe.Pointer(cDataJSON))
- }
-
- id, resultCh := f.registerChannel()
- C.SendNotificationWithActions(f.mainWindow.context, C.int(id), cIdentifier, cTitle, cSubtitle, cBody, cCategoryID, cDataJSON)
-
- select {
- case result := <-resultCh:
- close(resultCh)
- if !result.Success {
- if result.Error != nil {
- return result.Error
- }
- return fmt.Errorf("sending notification failed")
- }
- return nil
- case <-ctx.Done():
- f.cleanupChannel(id)
- return fmt.Errorf("sending notification timed out: %w", ctx.Err())
- }
-}
-
-// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
-// Registering a category with the same name as a previously registered NotificationCategory will override it.
-func (f *Frontend) RegisterNotificationCategory(category frontend.NotificationCategory) error {
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
-
- cCategoryID := C.CString(category.ID)
- defer C.free(unsafe.Pointer(cCategoryID))
-
- actionsJSON, err := json.Marshal(category.Actions)
- if err != nil {
- return fmt.Errorf("failed to marshal notification category: %w", err)
- }
- cActionsJSON := C.CString(string(actionsJSON))
- defer C.free(unsafe.Pointer(cActionsJSON))
-
- var cReplyPlaceholder, cReplyButtonTitle *C.char
- if category.HasReplyField {
- cReplyPlaceholder = C.CString(category.ReplyPlaceholder)
- cReplyButtonTitle = C.CString(category.ReplyButtonTitle)
- defer C.free(unsafe.Pointer(cReplyPlaceholder))
- defer C.free(unsafe.Pointer(cReplyButtonTitle))
- }
-
- id, resultCh := f.registerChannel()
- C.RegisterNotificationCategory(f.mainWindow.context, C.int(id), cCategoryID, cActionsJSON, C.bool(category.HasReplyField),
- cReplyPlaceholder, cReplyButtonTitle)
-
- select {
- case result := <-resultCh:
- close(resultCh)
- if !result.Success {
- if result.Error != nil {
- return result.Error
- }
- return fmt.Errorf("category registration failed")
- }
- return nil
- case <-ctx.Done():
- f.cleanupChannel(id)
- return fmt.Errorf("category registration timed out: %w", ctx.Err())
- }
-}
-
-// RemoveNotificationCategory remove a previously registered NotificationCategory.
-func (f *Frontend) RemoveNotificationCategory(categoryId string) error {
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
-
- cCategoryID := C.CString(categoryId)
- defer C.free(unsafe.Pointer(cCategoryID))
-
- id, resultCh := f.registerChannel()
- C.RemoveNotificationCategory(f.mainWindow.context, C.int(id), cCategoryID)
-
- select {
- case result := <-resultCh:
- close(resultCh)
- if !result.Success {
- if result.Error != nil {
- return result.Error
- }
- return fmt.Errorf("category removal failed")
- }
- return nil
- case <-ctx.Done():
- f.cleanupChannel(id)
- return fmt.Errorf("category removal timed out: %w", ctx.Err())
- }
-}
-
-// RemoveAllPendingNotifications removes all pending notifications.
-func (f *Frontend) RemoveAllPendingNotifications() error {
- C.RemoveAllPendingNotifications(f.mainWindow.context)
- return nil
-}
-
-// RemovePendingNotification removes a pending notification matching the unique identifier.
-func (f *Frontend) RemovePendingNotification(identifier string) error {
- cIdentifier := C.CString(identifier)
- defer C.free(unsafe.Pointer(cIdentifier))
- C.RemovePendingNotification(f.mainWindow.context, cIdentifier)
- return nil
-}
-
-// RemoveAllDeliveredNotifications removes all delivered notifications.
-func (f *Frontend) RemoveAllDeliveredNotifications() error {
- C.RemoveAllDeliveredNotifications(f.mainWindow.context)
- return nil
-}
-
-// RemoveDeliveredNotification removes a delivered notification matching the unique identifier.
-func (f *Frontend) RemoveDeliveredNotification(identifier string) error {
- cIdentifier := C.CString(identifier)
- defer C.free(unsafe.Pointer(cIdentifier))
- C.RemoveDeliveredNotification(f.mainWindow.context, cIdentifier)
- return nil
-}
-
-// RemoveNotification is a macOS stub that always returns nil.
-// Use one of the following instead:
-// RemoveAllPendingNotifications
-// RemovePendingNotification
-// RemoveAllDeliveredNotifications
-// RemoveDeliveredNotification
-// (Linux-specific)
-func (f *Frontend) RemoveNotification(identifier string) error {
- return nil
-}
-
-func (f *Frontend) OnNotificationResponse(callback func(result frontend.NotificationResult)) {
- callbackLock.Lock()
- notificationResultCallback = callback
- callbackLock.Unlock()
-}
-
-//export captureResult
-func captureResult(channelID C.int, success C.bool, errorMsg *C.char) {
- f := getCurrentFrontend()
- if f == nil {
- return
- }
-
- resultCh, exists := f.GetChannel(int(channelID))
- if !exists {
- return
- }
-
- var err error
- if errorMsg != nil {
- err = fmt.Errorf("%s", C.GoString(errorMsg))
- }
-
- resultCh <- notificationChannel{
- Success: bool(success),
- Error: err,
- }
-}
-
-//export didReceiveNotificationResponse
-func didReceiveNotificationResponse(jsonPayload *C.char, err *C.char) {
- result := frontend.NotificationResult{}
-
- if err != nil {
- errMsg := C.GoString(err)
- result.Error = fmt.Errorf("notification response error: %s", errMsg)
- handleNotificationResult(result)
-
- return
- }
-
- if jsonPayload == nil {
- result.Error = fmt.Errorf("received nil JSON payload in notification response")
- handleNotificationResult(result)
- return
- }
-
- payload := C.GoString(jsonPayload)
-
- var response frontend.NotificationResponse
- if err := json.Unmarshal([]byte(payload), &response); err != nil {
- result.Error = fmt.Errorf("failed to unmarshal notification response: %w", err)
- handleNotificationResult(result)
- return
- }
-
- if response.ActionIdentifier == AppleDefaultActionIdentifier {
- response.ActionIdentifier = DefaultActionIdentifier
- }
-
- result.Response = response
- handleNotificationResult(result)
-}
-
-func handleNotificationResult(result frontend.NotificationResult) {
- callbackLock.Lock()
- callback := notificationResultCallback
- callbackLock.Unlock()
-
- if callback != nil {
- go func() {
- defer func() {
- if r := recover(); r != nil {
- // Log panic but don't crash the app
- fmt.Fprintf(os.Stderr, "panic in notification callback: %v\n", r)
- }
- }()
- callback(result)
- }()
- }
-}
-
-// Helper methods
-
-func (f *Frontend) registerChannel() (int, chan notificationChannel) {
- channelsLock.Lock()
- defer channelsLock.Unlock()
-
- // Initialize channels map if it's nil
- if channels == nil {
- channels = make(map[int]chan notificationChannel)
- nextChannelID = 0
- }
-
- id := nextChannelID
- nextChannelID++
-
- resultCh := make(chan notificationChannel, 1)
-
- channels[id] = resultCh
- return id, resultCh
-}
-
-func (f *Frontend) GetChannel(id int) (chan notificationChannel, bool) {
- channelsLock.Lock()
- defer channelsLock.Unlock()
-
- if channels == nil {
- return nil, false
- }
-
- ch, exists := channels[id]
- if exists {
- delete(channels, id)
- }
- return ch, exists
-}
-
-func (f *Frontend) cleanupChannel(id int) {
- channelsLock.Lock()
- defer channelsLock.Unlock()
-
- if channels == nil {
- return
- }
-
- if ch, exists := channels[id]; exists {
- delete(channels, id)
- close(ch)
- }
-}
diff --git a/v2/internal/frontend/desktop/linux/notifications.go b/v2/internal/frontend/desktop/linux/notifications.go
deleted file mode 100644
index 80f0ae569..000000000
--- a/v2/internal/frontend/desktop/linux/notifications.go
+++ /dev/null
@@ -1,594 +0,0 @@
-//go:build linux
-// +build linux
-
-package linux
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "os"
- "path/filepath"
- "sync"
-
- "github.com/godbus/dbus/v5"
- "github.com/wailsapp/wails/v2/internal/frontend"
-)
-
-var (
- conn *dbus.Conn
- categories map[string]frontend.NotificationCategory = make(map[string]frontend.NotificationCategory)
- categoriesLock sync.RWMutex
- notifications map[uint32]*notificationData = make(map[uint32]*notificationData)
- notificationsLock sync.RWMutex
- notificationResultCallback func(result frontend.NotificationResult)
- callbackLock sync.RWMutex
- appName string
- cancel context.CancelFunc
-)
-
-type notificationData struct {
- ID string
- Title string
- Subtitle string
- Body string
- CategoryID string
- Data map[string]interface{}
- DBusID uint32
- ActionMap map[string]string
-}
-
-const (
- dbusNotificationInterface = "org.freedesktop.Notifications"
- dbusNotificationPath = "/org/freedesktop/Notifications"
- DefaultActionIdentifier = "DEFAULT_ACTION"
-)
-
-// Creates a new Notifications Service.
-func (f *Frontend) InitializeNotifications() error {
- // Clean up any previous initialization
- f.CleanupNotifications()
-
- exe, err := os.Executable()
- if err != nil {
- return fmt.Errorf("failed to get executable: %w", err)
- }
- appName = filepath.Base(exe)
-
- _conn, err := dbus.ConnectSessionBus()
- if err != nil {
- return fmt.Errorf("failed to connect to session bus: %w", err)
- }
- conn = _conn
-
- if err := f.loadCategories(); err != nil {
- f.logger.Warning("Failed to load notification categories: %v", err)
- }
-
- var signalCtx context.Context
- signalCtx, cancel = context.WithCancel(context.Background())
-
- if err := f.setupSignalHandling(signalCtx); err != nil {
- return fmt.Errorf("failed to set up notification signal handling: %w", err)
- }
-
- return nil
-}
-
-// CleanupNotifications cleans up notification resources
-func (f *Frontend) CleanupNotifications() {
- if cancel != nil {
- cancel()
- cancel = nil
- }
-
- if conn != nil {
- conn.Close()
- conn = nil
- }
-}
-
-func (f *Frontend) IsNotificationAvailable() bool {
- return true
-}
-
-// RequestNotificationAuthorization is a Linux stub that always returns true, nil.
-// (authorization is macOS-specific)
-func (f *Frontend) RequestNotificationAuthorization() (bool, error) {
- return true, nil
-}
-
-// CheckNotificationAuthorization is a Linux stub that always returns true.
-// (authorization is macOS-specific)
-func (f *Frontend) CheckNotificationAuthorization() (bool, error) {
- return true, nil
-}
-
-// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body.
-func (f *Frontend) SendNotification(options frontend.NotificationOptions) error {
- if conn == nil {
- return fmt.Errorf("notifications not initialized")
- }
-
- hints := map[string]dbus.Variant{}
-
- body := options.Body
- if options.Subtitle != "" {
- body = options.Subtitle + "\n" + body
- }
-
- defaultActionID := "default"
- actions := []string{defaultActionID, "Default"}
-
- actionMap := map[string]string{
- defaultActionID: DefaultActionIdentifier,
- }
-
- hints["x-notification-id"] = dbus.MakeVariant(options.ID)
-
- if options.Data != nil {
- userData, err := json.Marshal(options.Data)
- if err == nil {
- hints["x-user-data"] = dbus.MakeVariant(string(userData))
- }
- }
-
- // Call the Notify method on the D-Bus interface
- obj := conn.Object(dbusNotificationInterface, dbusNotificationPath)
- call := obj.Call(
- dbusNotificationInterface+".Notify",
- 0,
- appName,
- uint32(0),
- "", // Icon
- options.Title,
- body,
- actions,
- hints,
- int32(-1),
- )
-
- if call.Err != nil {
- return fmt.Errorf("failed to send notification: %w", call.Err)
- }
-
- var dbusID uint32
- if err := call.Store(&dbusID); err != nil {
- return fmt.Errorf("failed to store notification ID: %w", err)
- }
-
- notification := ¬ificationData{
- ID: options.ID,
- Title: options.Title,
- Subtitle: options.Subtitle,
- Body: options.Body,
- Data: options.Data,
- DBusID: dbusID,
- ActionMap: actionMap,
- }
-
- notificationsLock.Lock()
- notifications[dbusID] = notification
- notificationsLock.Unlock()
-
- return nil
-}
-
-// SendNotificationWithActions sends a notification with additional actions.
-func (f *Frontend) SendNotificationWithActions(options frontend.NotificationOptions) error {
- if conn == nil {
- return fmt.Errorf("notifications not initialized")
- }
-
- categoriesLock.RLock()
- category, exists := categories[options.CategoryID]
- categoriesLock.RUnlock()
-
- if options.CategoryID == "" || !exists {
- // Fall back to basic notification
- return f.SendNotification(options)
- }
-
- body := options.Body
- if options.Subtitle != "" {
- body = options.Subtitle + "\n" + body
- }
-
- var actions []string
- actionMap := make(map[string]string)
-
- defaultActionID := "default"
- actions = append(actions, defaultActionID, "Default")
- actionMap[defaultActionID] = DefaultActionIdentifier
-
- for _, action := range category.Actions {
- actions = append(actions, action.ID, action.Title)
- actionMap[action.ID] = action.ID
- }
-
- hints := map[string]dbus.Variant{}
-
- hints["x-notification-id"] = dbus.MakeVariant(options.ID)
-
- hints["x-category-id"] = dbus.MakeVariant(options.CategoryID)
-
- if options.Data != nil {
- userData, err := json.Marshal(options.Data)
- if err == nil {
- hints["x-user-data"] = dbus.MakeVariant(string(userData))
- }
- }
-
- obj := conn.Object(dbusNotificationInterface, dbusNotificationPath)
- call := obj.Call(
- dbusNotificationInterface+".Notify",
- 0,
- appName,
- uint32(0),
- "", // Icon
- options.Title,
- body,
- actions,
- hints,
- int32(-1),
- )
-
- if call.Err != nil {
- return fmt.Errorf("failed to send notification: %w", call.Err)
- }
-
- var dbusID uint32
- if err := call.Store(&dbusID); err != nil {
- return fmt.Errorf("failed to store notification ID: %w", err)
- }
-
- notification := ¬ificationData{
- ID: options.ID,
- Title: options.Title,
- Subtitle: options.Subtitle,
- Body: options.Body,
- CategoryID: options.CategoryID,
- Data: options.Data,
- DBusID: dbusID,
- ActionMap: actionMap,
- }
-
- notificationsLock.Lock()
- notifications[dbusID] = notification
- notificationsLock.Unlock()
-
- return nil
-}
-
-// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
-func (f *Frontend) RegisterNotificationCategory(category frontend.NotificationCategory) error {
- categoriesLock.Lock()
- categories[category.ID] = category
- categoriesLock.Unlock()
-
- if err := f.saveCategories(); err != nil {
- f.logger.Warning("Failed to save notification categories: %v", err)
- }
-
- return nil
-}
-
-// RemoveNotificationCategory removes a previously registered NotificationCategory.
-func (f *Frontend) RemoveNotificationCategory(categoryId string) error {
- categoriesLock.Lock()
- delete(categories, categoryId)
- categoriesLock.Unlock()
-
- if err := f.saveCategories(); err != nil {
- f.logger.Warning("Failed to save notification categories: %v", err)
- }
-
- return nil
-}
-
-// RemoveAllPendingNotifications attempts to remove all active notifications.
-func (f *Frontend) RemoveAllPendingNotifications() error {
- notificationsLock.Lock()
- dbusIDs := make([]uint32, 0, len(notifications))
- for id := range notifications {
- dbusIDs = append(dbusIDs, id)
- }
- notificationsLock.Unlock()
-
- for _, id := range dbusIDs {
- f.closeNotification(id)
- }
-
- return nil
-}
-
-// RemovePendingNotification removes a pending notification.
-func (f *Frontend) RemovePendingNotification(identifier string) error {
- var dbusID uint32
- found := false
-
- notificationsLock.Lock()
- for id, notif := range notifications {
- if notif.ID == identifier {
- dbusID = id
- found = true
- break
- }
- }
- notificationsLock.Unlock()
-
- if !found {
- return nil
- }
-
- return f.closeNotification(dbusID)
-}
-
-// RemoveAllDeliveredNotifications functionally equivalent to RemoveAllPendingNotification on Linux.
-func (f *Frontend) RemoveAllDeliveredNotifications() error {
- return f.RemoveAllPendingNotifications()
-}
-
-// RemoveDeliveredNotification functionally equivalent RemovePendingNotification on Linux.
-func (f *Frontend) RemoveDeliveredNotification(identifier string) error {
- return f.RemovePendingNotification(identifier)
-}
-
-// RemoveNotification removes a notification by identifier.
-func (f *Frontend) RemoveNotification(identifier string) error {
- return f.RemovePendingNotification(identifier)
-}
-
-func (f *Frontend) OnNotificationResponse(callback func(result frontend.NotificationResult)) {
- callbackLock.Lock()
- defer callbackLock.Unlock()
-
- notificationResultCallback = callback
-}
-
-// Helper method to close a notification.
-func (f *Frontend) closeNotification(id uint32) error {
- if conn == nil {
- return fmt.Errorf("notifications not initialized")
- }
-
- obj := conn.Object(dbusNotificationInterface, dbusNotificationPath)
- call := obj.Call(dbusNotificationInterface+".CloseNotification", 0, id)
-
- if call.Err != nil {
- return fmt.Errorf("failed to close notification: %w", call.Err)
- }
-
- return nil
-}
-
-func (f *Frontend) getConfigDir() (string, error) {
- configDir, err := os.UserConfigDir()
- if err != nil {
- return "", fmt.Errorf("failed to get user config directory: %w", err)
- }
-
- appConfigDir := filepath.Join(configDir, appName)
- if err := os.MkdirAll(appConfigDir, 0755); err != nil {
- return "", fmt.Errorf("failed to create app config directory: %w", err)
- }
-
- return appConfigDir, nil
-}
-
-// Save notification categories.
-func (f *Frontend) saveCategories() error {
- configDir, err := f.getConfigDir()
- if err != nil {
- return err
- }
-
- categoriesFile := filepath.Join(configDir, "notification-categories.json")
-
- categoriesLock.RLock()
- categoriesData, err := json.MarshalIndent(categories, "", " ")
- categoriesLock.RUnlock()
-
- if err != nil {
- return fmt.Errorf("failed to marshal notification categories: %w", err)
- }
-
- if err := os.WriteFile(categoriesFile, categoriesData, 0644); err != nil {
- return fmt.Errorf("failed to write notification categories to disk: %w", err)
- }
-
- return nil
-}
-
-// Load notification categories.
-func (f *Frontend) loadCategories() error {
- configDir, err := f.getConfigDir()
- if err != nil {
- return err
- }
-
- categoriesFile := filepath.Join(configDir, "notification-categories.json")
-
- if _, err := os.Stat(categoriesFile); os.IsNotExist(err) {
- return nil
- }
-
- categoriesData, err := os.ReadFile(categoriesFile)
- if err != nil {
- return fmt.Errorf("failed to read notification categories from disk: %w", err)
- }
-
- _categories := make(map[string]frontend.NotificationCategory)
- if err := json.Unmarshal(categoriesData, &_categories); err != nil {
- return fmt.Errorf("failed to unmarshal notification categories: %w", err)
- }
-
- categoriesLock.Lock()
- categories = _categories
- categoriesLock.Unlock()
-
- return nil
-}
-
-// Setup signal handling for notification actions.
-func (f *Frontend) setupSignalHandling(ctx context.Context) error {
- if err := conn.AddMatchSignal(
- dbus.WithMatchInterface(dbusNotificationInterface),
- dbus.WithMatchMember("ActionInvoked"),
- ); err != nil {
- return err
- }
-
- if err := conn.AddMatchSignal(
- dbus.WithMatchInterface(dbusNotificationInterface),
- dbus.WithMatchMember("NotificationClosed"),
- ); err != nil {
- return err
- }
-
- c := make(chan *dbus.Signal, 10)
- conn.Signal(c)
-
- go f.handleSignals(ctx, c)
-
- return nil
-}
-
-// Handle incoming D-Bus signals.
-func (f *Frontend) handleSignals(ctx context.Context, c chan *dbus.Signal) {
- for {
- select {
- case <-ctx.Done():
- return
- case signal, ok := <-c:
- if !ok {
- return
- }
-
- switch signal.Name {
- case dbusNotificationInterface + ".ActionInvoked":
- f.handleActionInvoked(signal)
- case dbusNotificationInterface + ".NotificationClosed":
- f.handleNotificationClosed(signal)
- }
- }
- }
-}
-
-// Handle ActionInvoked signal.
-func (f *Frontend) handleActionInvoked(signal *dbus.Signal) {
- if len(signal.Body) < 2 {
- return
- }
-
- dbusID, ok := signal.Body[0].(uint32)
- if !ok {
- return
- }
-
- actionID, ok := signal.Body[1].(string)
- if !ok {
- return
- }
-
- notificationsLock.Lock()
- notification, exists := notifications[dbusID]
- if exists {
- delete(notifications, dbusID)
- }
- notificationsLock.Unlock()
-
- if !exists {
- return
- }
-
- appActionID, ok := notification.ActionMap[actionID]
- if !ok {
- appActionID = actionID
- }
-
- response := frontend.NotificationResponse{
- ID: notification.ID,
- ActionIdentifier: appActionID,
- Title: notification.Title,
- Subtitle: notification.Subtitle,
- Body: notification.Body,
- CategoryID: notification.CategoryID,
- UserInfo: notification.Data,
- }
-
- result := frontend.NotificationResult{
- Response: response,
- }
-
- handleNotificationResult(result)
-}
-
-func handleNotificationResult(result frontend.NotificationResult) {
- callbackLock.Lock()
- callback := notificationResultCallback
- callbackLock.Unlock()
-
- if callback != nil {
- go func() {
- defer func() {
- if r := recover(); r != nil {
- // Log panic but don't crash the app
- fmt.Fprintf(os.Stderr, "panic in notification callback: %v\n", r)
- }
- }()
- callback(result)
- }()
- }
-}
-
-// Handle NotificationClosed signal.
-// Reason codes:
-// 1 - expired timeout
-// 2 - dismissed by user (click on X)
-// 3 - closed by CloseNotification call
-// 4 - undefined/reserved
-func (f *Frontend) handleNotificationClosed(signal *dbus.Signal) {
- if len(signal.Body) < 2 {
- return
- }
-
- dbusID, ok := signal.Body[0].(uint32)
- if !ok {
- return
- }
-
- reason, ok := signal.Body[1].(uint32)
- if !ok {
- reason = 0 // Unknown reason
- }
-
- notificationsLock.Lock()
- notification, exists := notifications[dbusID]
- if exists {
- delete(notifications, dbusID)
- }
- notificationsLock.Unlock()
-
- if !exists {
- return
- }
-
- if reason == 2 {
- response := frontend.NotificationResponse{
- ID: notification.ID,
- ActionIdentifier: DefaultActionIdentifier,
- Title: notification.Title,
- Subtitle: notification.Subtitle,
- Body: notification.Body,
- CategoryID: notification.CategoryID,
- UserInfo: notification.Data,
- }
-
- result := frontend.NotificationResult{
- Response: response,
- }
-
- handleNotificationResult(result)
- }
-}
diff --git a/v2/internal/frontend/desktop/windows/notifications.go b/v2/internal/frontend/desktop/windows/notifications.go
deleted file mode 100644
index 0176b7077..000000000
--- a/v2/internal/frontend/desktop/windows/notifications.go
+++ /dev/null
@@ -1,489 +0,0 @@
-//go:build windows
-// +build windows
-
-package windows
-
-import (
- "encoding/base64"
- "encoding/json"
- "log"
- "sync"
-
- wintoast "git.sr.ht/~jackmordaunt/go-toast/v2/wintoast"
- "github.com/google/uuid"
- "github.com/wailsapp/wails/v2/internal/frontend"
- "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
- "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
-
- "fmt"
- "os"
- "path/filepath"
- _ "unsafe" // for go:linkname
-
- "git.sr.ht/~jackmordaunt/go-toast/v2"
- "golang.org/x/sys/windows/registry"
-)
-
-var (
- categories map[string]frontend.NotificationCategory
- categoriesLock sync.RWMutex
- appName string
- appGUID string
- iconPath string = ""
- exePath string
- iconOnce sync.Once
- iconErr error
-
- notificationResultCallback func(result frontend.NotificationResult)
- callbackLock sync.RWMutex
-)
-
-const DefaultActionIdentifier = "DEFAULT_ACTION"
-
-const (
- ToastRegistryPath = `Software\Classes\AppUserModelId\`
- ToastRegistryGuidKey = "CustomActivator"
- NotificationCategoriesRegistryPath = `SOFTWARE\%s\NotificationCategories`
- NotificationCategoriesRegistryKey = "Categories"
-)
-
-// NotificationPayload combines the action ID and user data into a single structure
-type NotificationPayload struct {
- Action string `json:"action"`
- Options frontend.NotificationOptions `json:"payload,omitempty"`
-}
-
-func (f *Frontend) InitializeNotifications() error {
- categoriesLock.Lock()
- defer categoriesLock.Unlock()
- categories = make(map[string]frontend.NotificationCategory)
-
- exe, err := os.Executable()
- if err != nil {
- return fmt.Errorf("failed to get executable: %w", err)
- }
- exePath = exe
- appName = filepath.Base(exePath)
-
- appGUID, err = getGUID()
- if err != nil {
- return err
- }
-
- iconPath = filepath.Join(os.TempDir(), appName+appGUID+".png")
-
- // Create the registry key for the toast activator
- key, _, err := registry.CreateKey(registry.CURRENT_USER,
- `Software\Classes\CLSID\`+appGUID+`\LocalServer32`, registry.ALL_ACCESS)
- if err != nil {
- return fmt.Errorf("failed to create CLSID key: %w", err)
- }
- defer key.Close()
-
- if err := key.SetStringValue("", fmt.Sprintf("\"%s\" %%1", exePath)); err != nil {
- return fmt.Errorf("failed to set CLSID server path: %w", err)
- }
-
- toast.SetAppData(toast.AppData{
- AppID: appName,
- GUID: appGUID,
- IconPath: iconPath,
- ActivationExe: exePath,
- })
-
- toast.SetActivationCallback(func(args string, data []toast.UserData) {
- result := frontend.NotificationResult{}
-
- actionIdentifier, options, err := parseNotificationResponse(args)
-
- if err != nil {
- result.Error = err
- } else {
- // Subtitle is retained but was not shown with the notification
- response := frontend.NotificationResponse{
- ID: options.ID,
- ActionIdentifier: actionIdentifier,
- Title: options.Title,
- Subtitle: options.Subtitle,
- Body: options.Body,
- CategoryID: options.CategoryID,
- UserInfo: options.Data,
- }
-
- if userText, found := getUserText(data); found {
- response.UserText = userText
- }
-
- result.Response = response
- }
-
- handleNotificationResult(result)
- })
-
- // Register the COM class factory for toast activation.
- // This is required for Windows to activate the app when users interact with notifications.
- // The go-toast library's SetAppData and SetActivationCallback handle the callback setup,
- // but the COM class factory registration is not exposed via public APIs, so we use
- // go:linkname to access the internal registerClassFactory function.
- if err := registerToastClassFactory(wintoast.ClassFactory); err != nil {
- return fmt.Errorf("CoRegisterClassObject failed: %w", err)
- }
-
- return loadCategoriesFromRegistry()
-}
-
-// registerToastClassFactory registers the COM class factory required for Windows toast notification activation.
-// This function uses go:linkname to access the unexported registerClassFactory function from go-toast.
-// The class factory is necessary for Windows COM activation when users click notification actions.
-// Without this registration, notification actions will not activate the application.
-//
-// This is a workaround until go-toast exports this functionality via a public API.
-// See: https://git.sr.ht/~jackmordaunt/go-toast
-//
-//go:linkname registerToastClassFactory git.sr.ht/~jackmordaunt/go-toast/v2/wintoast.registerClassFactory
-func registerToastClassFactory(factory *wintoast.IClassFactory) error
-
-// CleanupNotifications is a Windows stub that does nothing.
-// (Linux-specific cleanup)
-func (f *Frontend) CleanupNotifications() {
- // No cleanup needed on Windows
-}
-
-func (f *Frontend) IsNotificationAvailable() bool {
- return true
-}
-
-func (f *Frontend) RequestNotificationAuthorization() (bool, error) {
- return true, nil
-}
-
-func (f *Frontend) CheckNotificationAuthorization() (bool, error) {
- return true, nil
-}
-
-// SendNotification sends a basic notification with a name, title, and body. All other options are ignored on Windows.
-// (subtitle is only available on macOS and Linux)
-func (f *Frontend) SendNotification(options frontend.NotificationOptions) error {
- if err := f.saveIconToDir(); err != nil {
- f.logger.Warning("Error saving icon: %v", err)
- }
-
- n := toast.Notification{
- Title: options.Title,
- Body: options.Body,
- ActivationType: toast.Foreground,
- ActivationArguments: DefaultActionIdentifier,
- }
-
- encodedPayload, err := encodePayload(DefaultActionIdentifier, options)
- if err != nil {
- return fmt.Errorf("failed to encode notification payload: %w", err)
- }
- n.ActivationArguments = encodedPayload
-
- return n.Push()
-}
-
-// SendNotificationWithActions sends a notification with additional actions and inputs.
-// A NotificationCategory must be registered with RegisterNotificationCategory first. The `CategoryID` must match the registered category.
-// If a NotificationCategory is not registered a basic notification will be sent.
-// (subtitle is only available on macOS and Linux)
-func (f *Frontend) SendNotificationWithActions(options frontend.NotificationOptions) error {
- if err := f.saveIconToDir(); err != nil {
- f.logger.Warning("Error saving icon: %v", err)
- }
-
- categoriesLock.RLock()
- nCategory, categoryExists := categories[options.CategoryID]
- categoriesLock.RUnlock()
-
- if options.CategoryID == "" || !categoryExists {
- f.logger.Warning("Category '%s' not found, sending basic notification without actions", options.CategoryID)
- return f.SendNotification(options)
- }
-
- n := toast.Notification{
- Title: options.Title,
- Body: options.Body,
- ActivationType: toast.Foreground,
- ActivationArguments: DefaultActionIdentifier,
- }
-
- for _, action := range nCategory.Actions {
- n.Actions = append(n.Actions, toast.Action{
- Content: action.Title,
- Arguments: action.ID,
- })
- }
-
- if nCategory.HasReplyField {
- n.Inputs = append(n.Inputs, toast.Input{
- ID: "userText",
- Placeholder: nCategory.ReplyPlaceholder,
- })
-
- n.Actions = append(n.Actions, toast.Action{
- Content: nCategory.ReplyButtonTitle,
- Arguments: "TEXT_REPLY",
- InputID: "userText",
- })
- }
-
- encodedPayload, err := encodePayload(n.ActivationArguments, options)
- if err != nil {
- return fmt.Errorf("failed to encode notification payload: %w", err)
- }
- n.ActivationArguments = encodedPayload
-
- for index := range n.Actions {
- encodedPayload, err := encodePayload(n.Actions[index].Arguments, options)
- if err != nil {
- return fmt.Errorf("failed to encode notification payload: %w", err)
- }
- n.Actions[index].Arguments = encodedPayload
- }
-
- return n.Push()
-}
-
-// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
-// Registering a category with the same name as a previously registered NotificationCategory will override it.
-func (f *Frontend) RegisterNotificationCategory(category frontend.NotificationCategory) error {
- categoriesLock.Lock()
- defer categoriesLock.Unlock()
-
- categories[category.ID] = frontend.NotificationCategory{
- ID: category.ID,
- Actions: category.Actions,
- HasReplyField: category.HasReplyField,
- ReplyPlaceholder: category.ReplyPlaceholder,
- ReplyButtonTitle: category.ReplyButtonTitle,
- }
-
- return saveCategoriesToRegistry()
-}
-
-// RemoveNotificationCategory removes a previously registered NotificationCategory.
-func (f *Frontend) RemoveNotificationCategory(categoryId string) error {
- categoriesLock.Lock()
- defer categoriesLock.Unlock()
-
- delete(categories, categoryId)
-
- return saveCategoriesToRegistry()
-}
-
-// RemoveAllPendingNotifications is a Windows stub that always returns nil.
-// (macOS and Linux only)
-func (f *Frontend) RemoveAllPendingNotifications() error {
- return nil
-}
-
-// RemovePendingNotification is a Windows stub that always returns nil.
-// (macOS and Linux only)
-func (f *Frontend) RemovePendingNotification(_ string) error {
- return nil
-}
-
-// RemoveAllDeliveredNotifications is a Windows stub that always returns nil.
-// (macOS and Linux only)
-func (f *Frontend) RemoveAllDeliveredNotifications() error {
- return nil
-}
-
-// RemoveDeliveredNotification is a Windows stub that always returns nil.
-// (macOS and Linux only)
-func (f *Frontend) RemoveDeliveredNotification(_ string) error {
- return nil
-}
-
-// RemoveNotification is a Windows stub that always returns nil.
-// (Linux-specific)
-func (f *Frontend) RemoveNotification(identifier string) error {
- return nil
-}
-
-func (f *Frontend) OnNotificationResponse(callback func(result frontend.NotificationResult)) {
- callbackLock.Lock()
- defer callbackLock.Unlock()
-
- notificationResultCallback = callback
-}
-
-func (f *Frontend) saveIconToDir() error {
- iconOnce.Do(func() {
- hIcon := w32.ExtractIcon(exePath, 0)
- if hIcon == 0 {
- iconErr = fmt.Errorf("ExtractIcon failed for %s", exePath)
- return
- }
- defer w32.DestroyIcon(hIcon)
- iconErr = winc.SaveHIconAsPNG(hIcon, iconPath)
- })
- return iconErr
-}
-
-func saveCategoriesToRegistry() error {
- // We assume lock is held by caller
-
- registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, appName)
-
- key, _, err := registry.CreateKey(
- registry.CURRENT_USER,
- registryPath,
- registry.ALL_ACCESS,
- )
- if err != nil {
- return err
- }
- defer key.Close()
-
- data, err := json.Marshal(categories)
- if err != nil {
- return err
- }
-
- return key.SetStringValue(NotificationCategoriesRegistryKey, string(data))
-}
-
-func loadCategoriesFromRegistry() error {
- // We assume lock is held by caller
-
- registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, appName)
-
- key, err := registry.OpenKey(
- registry.CURRENT_USER,
- registryPath,
- registry.QUERY_VALUE,
- )
- if err != nil {
- if err == registry.ErrNotExist {
- // Not an error, no saved categories
- return nil
- }
- return fmt.Errorf("failed to open registry key: %w", err)
- }
- defer key.Close()
-
- data, _, err := key.GetStringValue(NotificationCategoriesRegistryKey)
- if err != nil {
- if err == registry.ErrNotExist {
- // No value yet, but key exists
- return nil
- }
- return fmt.Errorf("failed to read categories from registry: %w", err)
- }
-
- _categories := make(map[string]frontend.NotificationCategory)
- if err := json.Unmarshal([]byte(data), &_categories); err != nil {
- return fmt.Errorf("failed to parse notification categories from registry: %w", err)
- }
-
- categories = _categories
-
- return nil
-}
-
-func getUserText(data []toast.UserData) (string, bool) {
- for _, d := range data {
- if d.Key == "userText" {
- return d.Value, true
- }
- }
- return "", false
-}
-
-// encodePayload combines an action ID and user data into a single encoded string
-func encodePayload(actionID string, options frontend.NotificationOptions) (string, error) {
- payload := NotificationPayload{
- Action: actionID,
- Options: options,
- }
-
- jsonData, err := json.Marshal(payload)
- if err != nil {
- return actionID, err
- }
-
- encodedPayload := base64.StdEncoding.EncodeToString(jsonData)
- return encodedPayload, nil
-}
-
-// decodePayload extracts the action ID and user data from an encoded payload
-func decodePayload(encodedString string) (string, frontend.NotificationOptions, error) {
- jsonData, err := base64.StdEncoding.DecodeString(encodedString)
- if err != nil {
- return encodedString, frontend.NotificationOptions{}, fmt.Errorf("failed to decode base64 payload: %w", err)
- }
-
- var payload NotificationPayload
- if err := json.Unmarshal(jsonData, &payload); err != nil {
- return encodedString, frontend.NotificationOptions{}, fmt.Errorf("failed to unmarshal notification payload: %w", err)
- }
-
- return payload.Action, payload.Options, nil
-}
-
-// parseNotificationResponse updated to use structured payload decoding
-func parseNotificationResponse(response string) (action string, options frontend.NotificationOptions, err error) {
- actionID, options, err := decodePayload(response)
-
- if err != nil {
- log.Printf("Warning: Failed to decode notification response: %v", err)
- return response, frontend.NotificationOptions{}, err
- }
-
- return actionID, options, nil
-}
-
-func handleNotificationResult(result frontend.NotificationResult) {
- callbackLock.RLock()
- callback := notificationResultCallback
- callbackLock.RUnlock()
-
- if callback != nil {
- go func() {
- defer func() {
- if r := recover(); r != nil {
- // Log panic but don't crash the app
- fmt.Fprintf(os.Stderr, "panic in notification callback: %v\n", r)
- }
- }()
- callback(result)
- }()
- }
-}
-
-// Helper functions
-
-func getGUID() (string, error) {
- keyPath := ToastRegistryPath + appName
-
- k, err := registry.OpenKey(registry.CURRENT_USER, keyPath, registry.QUERY_VALUE)
- if err == nil {
- guid, _, err := k.GetStringValue(ToastRegistryGuidKey)
- k.Close()
- if err == nil && guid != "" {
- return guid, nil
- }
- }
-
- guid := generateGUID()
-
- k, _, err = registry.CreateKey(registry.CURRENT_USER, keyPath, registry.WRITE)
- if err != nil {
- return "", fmt.Errorf("failed to create registry key: %w", err)
- }
- defer k.Close()
-
- if err := k.SetStringValue(ToastRegistryGuidKey, guid); err != nil {
- return "", fmt.Errorf("failed to write GUID to registry: %w", err)
- }
-
- return guid, nil
-}
-
-func generateGUID() string {
- guid := uuid.New()
- return fmt.Sprintf("{%s}", guid.String())
-}
diff --git a/v2/internal/frontend/desktop/windows/winc/icon.go b/v2/internal/frontend/desktop/windows/winc/icon.go
index 94e9198d6..6a3e1a391 100644
--- a/v2/internal/frontend/desktop/windows/winc/icon.go
+++ b/v2/internal/frontend/desktop/windows/winc/icon.go
@@ -10,86 +10,11 @@ package winc
import (
"errors"
"fmt"
- "image"
- "image/png"
- "os"
"syscall"
- "unsafe"
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
)
-var (
- user32 = syscall.NewLazyDLL("user32.dll")
- gdi32 = syscall.NewLazyDLL("gdi32.dll")
- procGetIconInfo = user32.NewProc("GetIconInfo")
- procDeleteObject = gdi32.NewProc("DeleteObject")
- procGetObject = gdi32.NewProc("GetObjectW")
- procGetDIBits = gdi32.NewProc("GetDIBits")
- procCreateCompatibleDC = gdi32.NewProc("CreateCompatibleDC")
- procSelectObject = gdi32.NewProc("SelectObject")
- procDeleteDC = gdi32.NewProc("DeleteDC")
-)
-
-func init() {
- // Validate DLL loads at initialization time to surface missing APIs early
- if err := user32.Load(); err != nil {
- panic(fmt.Sprintf("failed to load user32.dll: %v", err))
- }
- if err := gdi32.Load(); err != nil {
- panic(fmt.Sprintf("failed to load gdi32.dll: %v", err))
- }
-}
-
-// ICONINFO mirrors the Win32 ICONINFO struct
-type ICONINFO struct {
- FIcon int32
- XHotspot uint32
- YHotspot uint32
- HbmMask uintptr
- HbmColor uintptr
-}
-
-// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183376.aspx
-type BITMAPINFOHEADER struct {
- BiSize uint32
- BiWidth int32
- BiHeight int32
- BiPlanes uint16
- BiBitCount uint16
- BiCompression uint32
- BiSizeImage uint32
- BiXPelsPerMeter int32
- BiYPelsPerMeter int32
- BiClrUsed uint32
- BiClrImportant uint32
-}
-
-// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162938.aspx
-type RGBQUAD struct {
- RgbBlue byte
- RgbGreen byte
- RgbRed byte
- RgbReserved byte
-}
-
-// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183375.aspx
-type BITMAPINFO struct {
- BmiHeader BITMAPINFOHEADER
- BmiColors *RGBQUAD
-}
-
-// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183371.aspx
-type BITMAP struct {
- BmType int32
- BmWidth int32
- BmHeight int32
- BmWidthBytes int32
- BmPlanes uint16
- BmBitsPixel uint16
- BmBits unsafe.Pointer
-}
-
type Icon struct {
handle w32.HICON
}
@@ -121,95 +46,6 @@ func ExtractIcon(fileName string, index int) (*Icon, error) {
return ico, err
}
-func SaveHIconAsPNG(hIcon w32.HICON, filePath string) error {
- // Get icon info
- var iconInfo ICONINFO
- ret, _, err := procGetIconInfo.Call(
- uintptr(hIcon),
- uintptr(unsafe.Pointer(&iconInfo)),
- )
- if ret == 0 {
- return err
- }
- defer procDeleteObject.Call(uintptr(iconInfo.HbmMask))
- defer procDeleteObject.Call(uintptr(iconInfo.HbmColor))
-
- // Get bitmap info
- var bmp BITMAP
- ret, _, err = procGetObject.Call(
- uintptr(iconInfo.HbmColor),
- unsafe.Sizeof(bmp),
- uintptr(unsafe.Pointer(&bmp)),
- )
- if ret == 0 {
- return err
- }
-
- // Get screen DC for GetDIBits (bitmap must not be selected into a DC)
- screenDC := w32.GetDC(0)
- if screenDC == 0 {
- return fmt.Errorf("failed to get screen DC")
- }
- defer w32.ReleaseDC(0, screenDC)
-
- // Prepare bitmap info header
- var bi BITMAPINFO
- bi.BmiHeader.BiSize = uint32(unsafe.Sizeof(bi.BmiHeader))
- bi.BmiHeader.BiWidth = bmp.BmWidth
- bi.BmiHeader.BiHeight = bmp.BmHeight
- bi.BmiHeader.BiPlanes = 1
- bi.BmiHeader.BiBitCount = 32
- bi.BmiHeader.BiCompression = w32.BI_RGB
-
- // Allocate memory for bitmap bits
- width, height := int(bmp.BmWidth), int(bmp.BmHeight)
- bufferSize := width * height * 4
- bits := make([]byte, bufferSize)
-
- // Get bitmap bits using screen DC (bitmap must not be selected into any DC)
- ret, _, err = procGetDIBits.Call(
- uintptr(screenDC),
- uintptr(iconInfo.HbmColor),
- 0,
- uintptr(bmp.BmHeight),
- uintptr(unsafe.Pointer(&bits[0])),
- uintptr(unsafe.Pointer(&bi)),
- w32.DIB_RGB_COLORS,
- )
- if ret == 0 {
- return fmt.Errorf("failed to get bitmap bits: %w", err)
- }
-
- // Create Go image
- img := image.NewRGBA(image.Rect(0, 0, width, height))
-
- // Convert DIB to RGBA
- for y := 0; y < height; y++ {
- for x := 0; x < width; x++ {
- // DIB is bottom-up, so we need to invert Y
- dibIndex := ((height-1-y)*width + x) * 4
- // RGBA image is top-down
- imgIndex := (y*width + x) * 4
-
- // BGRA to RGBA
- img.Pix[imgIndex] = bits[dibIndex+2] // R
- img.Pix[imgIndex+1] = bits[dibIndex+1] // G
- img.Pix[imgIndex+2] = bits[dibIndex] // B
- img.Pix[imgIndex+3] = bits[dibIndex+3] // A
- }
- }
-
- // Create output file
- outFile, err := os.Create(filePath)
- if err != nil {
- return err
- }
- defer outFile.Close()
-
- // Encode and save the image
- return png.Encode(outFile, img)
-}
-
func (ic *Icon) Destroy() bool {
return w32.DestroyIcon(ic.handle)
}
diff --git a/v2/internal/frontend/dispatcher/systemcalls.go b/v2/internal/frontend/dispatcher/systemcalls.go
index a13eb03b9..b090a416e 100644
--- a/v2/internal/frontend/dispatcher/systemcalls.go
+++ b/v2/internal/frontend/dispatcher/systemcalls.go
@@ -61,102 +61,6 @@ func (d *Dispatcher) processSystemCall(payload callMessage, sender frontend.Fron
return false, err
}
return true, nil
- case "InitializeNotifications":
- err := sender.InitializeNotifications()
- return nil, err
- case "CleanupNotifications":
- sender.CleanupNotifications()
- return nil, nil
- case "IsNotificationAvailable":
- return sender.IsNotificationAvailable(), nil
- case "RequestNotificationAuthorization":
- authorized, err := sender.RequestNotificationAuthorization()
- if err != nil {
- return nil, err
- }
- return authorized, nil
- case "CheckNotificationAuthorization":
- authorized, err := sender.CheckNotificationAuthorization()
- if err != nil {
- return nil, err
- }
- return authorized, nil
- case "SendNotification":
- if len(payload.Args) < 1 {
- return nil, errors.New("empty argument, cannot send notification")
- }
- var options frontend.NotificationOptions
- if err := json.Unmarshal(payload.Args[0], &options); err != nil {
- return nil, err
- }
- err := sender.SendNotification(options)
- return nil, err
- case "SendNotificationWithActions":
- if len(payload.Args) < 1 {
- return nil, errors.New("empty argument, cannot send notification")
- }
- var options frontend.NotificationOptions
- if err := json.Unmarshal(payload.Args[0], &options); err != nil {
- return nil, err
- }
- err := sender.SendNotificationWithActions(options)
- return nil, err
- case "RegisterNotificationCategory":
- if len(payload.Args) < 1 {
- return nil, errors.New("empty argument, cannot register category")
- }
- var category frontend.NotificationCategory
- if err := json.Unmarshal(payload.Args[0], &category); err != nil {
- return nil, err
- }
- err := sender.RegisterNotificationCategory(category)
- return nil, err
- case "RemoveNotificationCategory":
- if len(payload.Args) < 1 {
- return nil, errors.New("empty argument, cannot remove category")
- }
- var categoryId string
- if err := json.Unmarshal(payload.Args[0], &categoryId); err != nil {
- return nil, err
- }
- err := sender.RemoveNotificationCategory(categoryId)
- return nil, err
- case "RemoveAllPendingNotifications":
- err := sender.RemoveAllPendingNotifications()
- return nil, err
- case "RemovePendingNotification":
- if len(payload.Args) < 1 {
- return nil, errors.New("empty argument, cannot remove notification")
- }
- var identifier string
- if err := json.Unmarshal(payload.Args[0], &identifier); err != nil {
- return nil, err
- }
- err := sender.RemovePendingNotification(identifier)
- return nil, err
- case "RemoveAllDeliveredNotifications":
- err := sender.RemoveAllDeliveredNotifications()
- return nil, err
- case "RemoveDeliveredNotification":
- if len(payload.Args) < 1 {
- return nil, errors.New("empty argument, cannot remove notification")
- }
- var identifier string
- if err := json.Unmarshal(payload.Args[0], &identifier); err != nil {
- return nil, err
- }
- err := sender.RemoveDeliveredNotification(identifier)
- return nil, err
- case "RemoveNotification":
- if len(payload.Args) < 1 {
- return nil, errors.New("empty argument, cannot remove notification")
- }
- var identifier string
- if err := json.Unmarshal(payload.Args[0], &identifier); err != nil {
- return nil, err
- }
- err := sender.RemoveNotification(identifier)
- return nil, err
default:
return nil, fmt.Errorf("unknown systemcall message: %s", payload.Name)
}
diff --git a/v2/internal/frontend/frontend.go b/v2/internal/frontend/frontend.go
index 873b61dc7..6b2ccbcae 100644
--- a/v2/internal/frontend/frontend.go
+++ b/v2/internal/frontend/frontend.go
@@ -76,51 +76,6 @@ type MessageDialogOptions struct {
Icon []byte
}
-// NotificationOptions contains configuration for a notification.
-type NotificationOptions struct {
- ID string `json:"id"`
- Title string `json:"title"`
- Subtitle string `json:"subtitle,omitempty"` // (macOS and Linux only)
- Body string `json:"body,omitempty"`
- CategoryID string `json:"categoryId,omitempty"`
- Data map[string]interface{} `json:"data,omitempty"`
-}
-
-// NotificationAction represents an action button for a notification.
-type NotificationAction struct {
- ID string `json:"id,omitempty"`
- Title string `json:"title,omitempty"`
- Destructive bool `json:"destructive,omitempty"` // (macOS-specific)
-}
-
-// NotificationCategory groups actions for notifications.
-type NotificationCategory struct {
- ID string `json:"id,omitempty"`
- Actions []NotificationAction `json:"actions,omitempty"`
- HasReplyField bool `json:"hasReplyField,omitempty"`
- ReplyPlaceholder string `json:"replyPlaceholder,omitempty"`
- ReplyButtonTitle string `json:"replyButtonTitle,omitempty"`
-}
-
-// NotificationResponse represents the response sent by interacting with a notification.
-type NotificationResponse struct {
- ID string `json:"id,omitempty"`
- ActionIdentifier string `json:"actionIdentifier,omitempty"`
- CategoryID string `json:"categoryId,omitempty"` // Consistent with NotificationOptions
- Title string `json:"title,omitempty"`
- Subtitle string `json:"subtitle,omitempty"` // (macOS and Linux only)
- Body string `json:"body,omitempty"`
- UserText string `json:"userText,omitempty"`
- UserInfo map[string]interface{} `json:"userInfo,omitempty"`
-}
-
-// NotificationResult represents the result of a notification response,
-// returning the response or any errors that occurred.
-type NotificationResult struct {
- Response NotificationResponse
- Error error
-}
-
type Frontend interface {
Run(ctx context.Context) error
RunMainLoop()
@@ -184,21 +139,4 @@ type Frontend interface {
// Clipboard
ClipboardGetText() (string, error)
ClipboardSetText(text string) error
-
- // Notifications
- InitializeNotifications() error
- CleanupNotifications()
- IsNotificationAvailable() bool
- RequestNotificationAuthorization() (bool, error)
- CheckNotificationAuthorization() (bool, error)
- OnNotificationResponse(callback func(result NotificationResult))
- SendNotification(options NotificationOptions) error
- SendNotificationWithActions(options NotificationOptions) error
- RegisterNotificationCategory(category NotificationCategory) error
- RemoveNotificationCategory(categoryId string) error
- RemoveAllPendingNotifications() error
- RemovePendingNotification(identifier string) error
- RemoveAllDeliveredNotifications() error
- RemoveDeliveredNotification(identifier string) error
- RemoveNotification(identifier string) error
}
diff --git a/v2/internal/frontend/runtime/desktop/main.js b/v2/internal/frontend/runtime/desktop/main.js
index 405d5f60d..3fda7ef36 100644
--- a/v2/internal/frontend/runtime/desktop/main.js
+++ b/v2/internal/frontend/runtime/desktop/main.js
@@ -27,7 +27,6 @@ import * as Browser from "./browser";
import * as Clipboard from "./clipboard";
import * as DragAndDrop from "./draganddrop";
import * as ContextMenu from "./contextmenu";
-import * as Notifications from "./notifications";
export function Quit() {
window.WailsInvoke('Q');
@@ -53,7 +52,6 @@ window.runtime = {
...Screen,
...Clipboard,
...DragAndDrop,
- ...Notifications,
EventsOn,
EventsOnce,
EventsOnMultiple,
diff --git a/v2/internal/frontend/runtime/desktop/notifications.js b/v2/internal/frontend/runtime/desktop/notifications.js
deleted file mode 100644
index 25c01bb34..000000000
--- a/v2/internal/frontend/runtime/desktop/notifications.js
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- _ __ _ __
-| | / /___ _(_) /____
-| | /| / / __ `/ / / ___/
-| |/ |/ / /_/ / / (__ )
-|__/|__/\__,_/_/_/____/
-The electron alternative for Go
-(c) Lea Anthony 2019-present
-*/
-/* jshint esversion: 9 */
-
-import {Call} from "./calls";
-
-/**
- * Initialize the notification service for the application.
- * This must be called before sending any notifications.
- * On macOS, this also ensures the notification delegate is properly initialized.
- *
- * @export
- * @return {Promise}
- */
-export function InitializeNotifications() {
- return Call(":wails:InitializeNotifications");
-}
-
-/**
- * Clean up notification resources and release any held connections.
- * This should be called when shutting down the application to properly release resources
- * (primarily needed on Linux to close D-Bus connections).
- *
- * @export
- * @return {Promise}
- */
-export function CleanupNotifications() {
- return Call(":wails:CleanupNotifications");
-}
-
-/**
- * Check if notifications are available on the current platform.
- *
- * @export
- * @return {Promise} True if notifications are available, false otherwise
- */
-export function IsNotificationAvailable() {
- return Call(":wails:IsNotificationAvailable");
-}
-
-/**
- * Request notification authorization from the user.
- * On macOS, this prompts the user to allow notifications.
- * On other platforms, this always returns true.
- *
- * @export
- * @return {Promise} True if authorization was granted, false otherwise
- */
-export function RequestNotificationAuthorization() {
- return Call(":wails:RequestNotificationAuthorization");
-}
-
-/**
- * Check the current notification authorization status.
- * On macOS, this checks if the app has notification permissions.
- * On other platforms, this always returns true.
- *
- * @export
- * @return {Promise} True if authorized, false otherwise
- */
-export function CheckNotificationAuthorization() {
- return Call(":wails:CheckNotificationAuthorization");
-}
-
-/**
- * Send a basic notification with the given options.
- * The notification will display with the provided title, subtitle (if supported), and body text.
- *
- * @export
- * @param {Object} options - Notification options
- * @param {string} options.id - Unique identifier for the notification
- * @param {string} options.title - Notification title
- * @param {string} [options.subtitle] - Notification subtitle (macOS and Linux only)
- * @param {string} [options.body] - Notification body text
- * @param {string} [options.categoryId] - Category ID for action buttons (requires SendNotificationWithActions)
- * @param {Object} [options.data] - Additional user data to attach to the notification
- * @return {Promise}
- */
-export function SendNotification(options) {
- return Call(":wails:SendNotification", [options]);
-}
-
-/**
- * Send a notification with action buttons.
- * A NotificationCategory must be registered first using RegisterNotificationCategory.
- * The options.categoryId must match a previously registered category ID.
- * If the category is not found, a basic notification will be sent instead.
- *
- * @export
- * @param {Object} options - Notification options
- * @param {string} options.id - Unique identifier for the notification
- * @param {string} options.title - Notification title
- * @param {string} [options.subtitle] - Notification subtitle (macOS and Linux only)
- * @param {string} [options.body] - Notification body text
- * @param {string} options.categoryId - Category ID that matches a registered category
- * @param {Object} [options.data] - Additional user data to attach to the notification
- * @return {Promise}
- */
-export function SendNotificationWithActions(options) {
- return Call(":wails:SendNotificationWithActions", [options]);
-}
-
-/**
- * Register a notification category that can be used with SendNotificationWithActions.
- * Categories define the action buttons and optional reply fields that will appear on notifications.
- * Registering a category with the same ID as a previously registered category will override it.
- *
- * @export
- * @param {Object} category - Notification category definition
- * @param {string} category.id - Unique identifier for the category
- * @param {Array