mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
Merge 106eecf2cd into 4d0abeb37c
This commit is contained in:
commit
b214423760
54 changed files with 1414 additions and 105 deletions
4
v2/examples/tray-icon/.gitignore
vendored
Normal file
4
v2/examples/tray-icon/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
build/bin
|
||||
node_modules
|
||||
frontend/dist
|
||||
frontend/wailsjs
|
||||
19
v2/examples/tray-icon/README.md
Normal file
19
v2/examples/tray-icon/README.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# README
|
||||
|
||||
## About
|
||||
|
||||
This example tests tray icon behavior and runtime tray updates.
|
||||
|
||||
## Running
|
||||
|
||||
From `v2/examples/tray-icon`:
|
||||
|
||||
```bash
|
||||
go run -tags "desktop,production,webkit2_41" .
|
||||
```
|
||||
|
||||
Or from `v2`:
|
||||
|
||||
```bash
|
||||
go run -tags "desktop,production,webkit2_41" ./examples/tray-icon
|
||||
```
|
||||
16
v2/examples/tray-icon/app.go
Normal file
16
v2/examples/tray-icon/app.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package main
|
||||
|
||||
import "context"
|
||||
|
||||
// App stores runtime context for tray callbacks.
|
||||
type App struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
}
|
||||
|
||||
func (a *App) startup(ctx context.Context) {
|
||||
a.ctx = ctx
|
||||
}
|
||||
35
v2/examples/tray-icon/build/README.md
Normal file
35
v2/examples/tray-icon/build/README.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Build Directory
|
||||
|
||||
The build directory is used to house all the build files and assets for your application.
|
||||
|
||||
The structure is:
|
||||
|
||||
* bin - Output directory
|
||||
* darwin - macOS specific files
|
||||
* windows - Windows specific files
|
||||
|
||||
## Mac
|
||||
|
||||
The `darwin` directory holds files specific to Mac builds.
|
||||
These may be customised and used as part of the build. To return these files to the default state, simply delete them
|
||||
and
|
||||
build with `wails build`.
|
||||
|
||||
The directory contains the following files:
|
||||
|
||||
- `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`.
|
||||
- `Info.dev.plist` - same as the main plist file but used when building using `wails dev`.
|
||||
|
||||
## Windows
|
||||
|
||||
The `windows` directory contains the manifest and rc files used when building with `wails build`.
|
||||
These may be customised for your application. To return these files to the default state, simply delete them and
|
||||
build with `wails build`.
|
||||
|
||||
- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to
|
||||
use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file
|
||||
will be created using the `appicon.png` file in the build directory.
|
||||
- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`.
|
||||
- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer,
|
||||
as well as the application itself (right click the exe -> properties -> details)
|
||||
- `wails.exe.manifest` - The main application manifest file.
|
||||
BIN
v2/examples/tray-icon/build/appicon.png
Normal file
BIN
v2/examples/tray-icon/build/appicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
68
v2/examples/tray-icon/build/darwin/Info.dev.plist
Normal file
68
v2/examples/tray-icon/build/darwin/Info.dev.plist
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>{{.Info.ProductName}}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>{{.OutputFilename}}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.wails.{{.Name}}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>{{.Info.ProductVersion}}</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>{{.Info.Comments}}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>{{.Info.ProductVersion}}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>iconfile</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.13.0</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<string>true</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>{{.Info.Copyright}}</string>
|
||||
{{if .Info.FileAssociations}}
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
{{range .Info.FileAssociations}}
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>{{.Ext}}</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>{{.Name}}</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>{{.Role}}</string>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>{{.IconName}}</string>
|
||||
</dict>
|
||||
{{end}}
|
||||
</array>
|
||||
{{end}}
|
||||
{{if .Info.Protocols}}
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
{{range .Info.Protocols}}
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>com.wails.{{.Scheme}}</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>{{.Scheme}}</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>{{.Role}}</string>
|
||||
</dict>
|
||||
{{end}}
|
||||
</array>
|
||||
{{end}}
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
63
v2/examples/tray-icon/build/darwin/Info.plist
Normal file
63
v2/examples/tray-icon/build/darwin/Info.plist
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>{{.Info.ProductName}}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>{{.OutputFilename}}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.wails.{{.Name}}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>{{.Info.ProductVersion}}</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>{{.Info.Comments}}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>{{.Info.ProductVersion}}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>iconfile</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.13.0</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<string>true</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>{{.Info.Copyright}}</string>
|
||||
{{if .Info.FileAssociations}}
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
{{range .Info.FileAssociations}}
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>{{.Ext}}</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>{{.Name}}</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>{{.Role}}</string>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>{{.IconName}}</string>
|
||||
</dict>
|
||||
{{end}}
|
||||
</array>
|
||||
{{end}}
|
||||
{{if .Info.Protocols}}
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
{{range .Info.Protocols}}
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>com.wails.{{.Scheme}}</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>{{.Scheme}}</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>{{.Role}}</string>
|
||||
</dict>
|
||||
{{end}}
|
||||
</array>
|
||||
{{end}}
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
v2/examples/tray-icon/build/windows/icon.ico
Normal file
BIN
v2/examples/tray-icon/build/windows/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
15
v2/examples/tray-icon/build/windows/info.json
Normal file
15
v2/examples/tray-icon/build/windows/info.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"fixed": {
|
||||
"file_version": "{{.Info.ProductVersion}}"
|
||||
},
|
||||
"info": {
|
||||
"0000": {
|
||||
"ProductVersion": "{{.Info.ProductVersion}}",
|
||||
"CompanyName": "{{.Info.CompanyName}}",
|
||||
"FileDescription": "{{.Info.ProductName}}",
|
||||
"LegalCopyright": "{{.Info.Copyright}}",
|
||||
"ProductName": "{{.Info.ProductName}}",
|
||||
"Comments": "{{.Info.Comments}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
15
v2/examples/tray-icon/build/windows/wails.exe.manifest
Normal file
15
v2/examples/tray-icon/build/windows/wails.exe.manifest
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<assemblyIdentity type="win32" name="com.wails.{{.Name}}" version="{{.Info.ProductVersion}}.0" processorArchitecture="*"/>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- fallback for Windows 7 and 8 -->
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness> <!-- falls back to per-monitor if per-monitor v2 is not supported -->
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
</assembly>
|
||||
37
v2/examples/tray-icon/go.mod
Normal file
37
v2/examples/tray-icon/go.mod
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
module tray-icon
|
||||
|
||||
go 1.23
|
||||
|
||||
require github.com/wailsapp/wails/v2 v2.11.0
|
||||
|
||||
require (
|
||||
github.com/bep/debounce v1.2.1 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
||||
github.com/labstack/echo/v4 v4.13.3 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||
github.com/leaanthony/gosod v1.0.4 // indirect
|
||||
github.com/leaanthony/slicer v1.6.0 // indirect
|
||||
github.com/leaanthony/u v1.1.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/samber/lo v1.49.1 // indirect
|
||||
github.com/tkrajina/go-reflector v0.5.8 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.22 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
golang.org/x/crypto v0.33.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/wailsapp/wails/v2 => ../../
|
||||
79
v2/examples/tray-icon/go.sum
Normal file
79
v2/examples/tray-icon/go.sum
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
|
||||
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
|
||||
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
||||
github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI=
|
||||
github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw=
|
||||
github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js=
|
||||
github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
|
||||
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
||||
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
||||
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
|
||||
github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58=
|
||||
github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
158
v2/examples/tray-icon/main.go
Normal file
158
v2/examples/tray-icon/main.go
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
//go:embed all:frontend/dist
|
||||
var assets embed.FS
|
||||
|
||||
func resolveTrayIconPath() string {
|
||||
return resolveTrayAssetPath("tray-icon.png")
|
||||
}
|
||||
|
||||
func resolveTrayAssetPath(filename string) string {
|
||||
candidates := []string{
|
||||
filepath.Join("examples", "tray-icon", "trayicons", filename),
|
||||
filepath.Join("trayicons", filename),
|
||||
}
|
||||
|
||||
for _, p := range candidates {
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
if abs, err := filepath.Abs(p); err == nil {
|
||||
return abs
|
||||
}
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
return candidates[0]
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := NewApp()
|
||||
primaryIcon := resolveTrayIconPath()
|
||||
altIcon := resolveTrayAssetPath("tray-icon-alt.png")
|
||||
|
||||
state := struct {
|
||||
mu sync.Mutex
|
||||
mode int
|
||||
clicks int
|
||||
updates int
|
||||
}{}
|
||||
|
||||
var buildTray func() *menu.TrayMenu
|
||||
buildTray = func() *menu.TrayMenu {
|
||||
state.mu.Lock()
|
||||
mode := state.mode
|
||||
clicks := state.clicks
|
||||
updates := state.updates
|
||||
state.mu.Unlock()
|
||||
|
||||
icon := primaryIcon
|
||||
label := "TrayIcon-A"
|
||||
actionText := "Primary Action: switch to B"
|
||||
actionLog := "action A"
|
||||
if mode == 1 {
|
||||
icon = altIcon
|
||||
label = "TrayIcon-B"
|
||||
actionText = "Primary Action: switch to A"
|
||||
actionLog = "action B"
|
||||
}
|
||||
|
||||
trayMenu := menu.NewMenu()
|
||||
|
||||
trayMenu.AddText("Show", nil, func(_ *menu.CallbackData) {
|
||||
runtime.Show(app.ctx)
|
||||
})
|
||||
trayMenu.AddText(actionText, nil, func(_ *menu.CallbackData) {
|
||||
state.mu.Lock()
|
||||
state.mode = 1 - state.mode
|
||||
state.clicks++
|
||||
state.updates++
|
||||
currentClicks := state.clicks
|
||||
state.mu.Unlock()
|
||||
|
||||
fmt.Printf("[tray-test] %s clicked (clicks=%d)\n", actionLog, currentClicks)
|
||||
runtime.TraySetSystemTray(app.ctx, buildTray())
|
||||
})
|
||||
|
||||
trayMenu.AddText("Ping", nil, func(_ *menu.CallbackData) {
|
||||
fmt.Println("[tray-test] Ping clicked")
|
||||
})
|
||||
|
||||
trayMenu.AddText("Toggle icon + text now", nil, func(_ *menu.CallbackData) {
|
||||
state.mu.Lock()
|
||||
state.mode = 1 - state.mode
|
||||
state.clicks++
|
||||
state.updates++
|
||||
currentClicks := state.clicks
|
||||
state.mu.Unlock()
|
||||
|
||||
fmt.Printf("[tray-test] manual toggle clicked (clicks=%d)\n", currentClicks)
|
||||
runtime.TraySetSystemTray(app.ctx, buildTray())
|
||||
})
|
||||
|
||||
trayMenu.AddSeparator()
|
||||
trayMenu.AddText("Quit", nil, func(_ *menu.CallbackData) {
|
||||
runtime.Quit(app.ctx)
|
||||
})
|
||||
|
||||
return &menu.TrayMenu{
|
||||
Label: fmt.Sprintf("%s (%d)", label, updates),
|
||||
Tooltip: fmt.Sprintf("Mode=%s, Clicks=%d", label, clicks),
|
||||
Image: icon,
|
||||
Menu: trayMenu,
|
||||
}
|
||||
}
|
||||
|
||||
onStartup := func(ctx context.Context) {
|
||||
app.startup(ctx)
|
||||
|
||||
go func() {
|
||||
time.Sleep(4 * time.Second)
|
||||
state.mu.Lock()
|
||||
state.mode = 1 - state.mode
|
||||
state.updates++
|
||||
state.mu.Unlock()
|
||||
fmt.Println("[tray-test] timed update: 4s")
|
||||
runtime.TraySetSystemTray(app.ctx, buildTray())
|
||||
|
||||
time.Sleep(4 * time.Second)
|
||||
state.mu.Lock()
|
||||
state.mode = 1 - state.mode
|
||||
state.updates++
|
||||
state.mu.Unlock()
|
||||
fmt.Println("[tray-test] timed update: 8s")
|
||||
runtime.TraySetSystemTray(app.ctx, buildTray())
|
||||
}()
|
||||
}
|
||||
|
||||
err := wails.Run(&options.App{
|
||||
Title: "Wails Tray Icon Test",
|
||||
Width: 720,
|
||||
Height: 420,
|
||||
AssetServer: &assetserver.Options{
|
||||
Assets: assets,
|
||||
},
|
||||
OnStartup: onStartup,
|
||||
Tray: buildTray(),
|
||||
HideWindowOnClose: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("[tray-test] Error:", err)
|
||||
}
|
||||
}
|
||||
BIN
v2/examples/tray-icon/trayicons/tray-icon-alt.png
Normal file
BIN
v2/examples/tray-icon/trayicons/tray-icon-alt.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
13
v2/examples/tray-icon/trayicons/tray-icon-alt.svg
Normal file
13
v2/examples/tray-icon/trayicons/tray-icon-alt.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="tray icon alt">
|
||||
<defs>
|
||||
<linearGradient id="bg2" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#0f766e"/>
|
||||
<stop offset="100%" stop-color="#115e59"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<circle cx="32" cy="32" r="28" fill="url(#bg2)"/>
|
||||
<circle cx="32" cy="32" r="16" fill="#f8fafc" opacity="0.92"/>
|
||||
<path d="M20 45h24v4H20z" fill="#f59e0b"/>
|
||||
<path d="M22 20h20v4H22z" fill="#e2e8f0"/>
|
||||
<rect x="29" y="23" width="6" height="18" rx="3" fill="#0f172a"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 576 B |
BIN
v2/examples/tray-icon/trayicons/tray-icon.png
Normal file
BIN
v2/examples/tray-icon/trayicons/tray-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
12
v2/examples/tray-icon/trayicons/tray-icon.svg
Normal file
12
v2/examples/tray-icon/trayicons/tray-icon.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="tray icon">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#1f2937"/>
|
||||
<stop offset="100%" stop-color="#111827"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="4" y="4" width="56" height="56" rx="14" fill="url(#bg)"/>
|
||||
<circle cx="32" cy="32" r="21" fill="#10b981" opacity="0.15"/>
|
||||
<path d="M19 45V19h11.5c6.7 0 10.5 3.5 10.5 9.4 0 4.9-2.9 8-8 8h-6.2V45H19zm7.8-14.9h3.6c2.2 0 3.4-1.2 3.4-3.1 0-2-1.2-3.1-3.4-3.1h-3.6v6.2z" fill="#e5e7eb"/>
|
||||
<rect x="38" y="37" width="10" height="10" rx="3" fill="#f59e0b"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 660 B |
5
v2/examples/tray-icon/wails.json
Normal file
5
v2/examples/tray-icon/wails.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"$schema": "https://wails.io/schemas/config.v2.json",
|
||||
"name": "tray-icon",
|
||||
"outputfilename": "tray-icon"
|
||||
}
|
||||
|
|
@ -30,6 +30,9 @@ import (
|
|||
|
||||
func (a *App) Run() error {
|
||||
err := a.frontend.Run(a.ctx)
|
||||
if a.options.Tray != nil {
|
||||
a.frontend.TraySetSystemTray(a.options.Tray)
|
||||
}
|
||||
a.frontend.RunMainLoop()
|
||||
a.frontend.WindowClose()
|
||||
if a.shutdownCallback != nil {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
//go:build devtools
|
||||
|
||||
package app
|
||||
|
||||
// Note: devtools flag is also added in debug builds
|
||||
func IsDevtoolsEnabled() bool {
|
||||
return true
|
||||
}
|
||||
//go:build devtools
|
||||
|
||||
package app
|
||||
|
||||
// Note: devtools flag is also added in debug builds
|
||||
func IsDevtoolsEnabled() bool {
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ import (
|
|||
|
||||
func (a *App) Run() error {
|
||||
err := a.frontend.Run(a.ctx)
|
||||
if a.options.Tray != nil {
|
||||
a.frontend.TraySetSystemTray(a.options.Tray)
|
||||
}
|
||||
a.frontend.RunMainLoop()
|
||||
a.frontend.WindowClose()
|
||||
if a.shutdownCallback != nil {
|
||||
|
|
|
|||
|
|
@ -52,10 +52,10 @@ var AllMMiddleEnumValues = []struct {
|
|||
}
|
||||
|
||||
type EntityWithMultipleEnums struct {
|
||||
Name string `json:"name"`
|
||||
EnumZ ZFirstEnum `json:"enumZ"`
|
||||
EnumA ASecondEnum `json:"enumA"`
|
||||
EnumM MMiddleEnum `json:"enumM"`
|
||||
Name string `json:"name"`
|
||||
EnumZ ZFirstEnum `json:"enumZ"`
|
||||
EnumA ASecondEnum `json:"enumA"`
|
||||
EnumM MMiddleEnum `json:"enumM"`
|
||||
}
|
||||
|
||||
func (e EntityWithMultipleEnums) Get() EntityWithMultipleEnums {
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ void AppendRole(void *inctx, void *inMenu, int role);
|
|||
void SetAsApplicationMenu(void *inctx, void *inMenu);
|
||||
void UpdateApplicationMenu(void *inctx);
|
||||
|
||||
void TraySetSystemTray(void *inctx, const char* label, const char* image, int isTemplate, const char* tooltip, void *inMenu);
|
||||
|
||||
void SetAbout(void *inctx, const char* title, const char* description, void* imagedata, int datalen);
|
||||
void* AppendMenuItem(void* inctx, void* nsmenu, const char* label, const char* shortcutKey, int modifiers, int disabled, int checked, int menuItemID);
|
||||
void AppendSeparator(void* inMenu);
|
||||
|
|
|
|||
|
|
@ -336,6 +336,52 @@ void UpdateApplicationMenu(void *inctx) {
|
|||
)
|
||||
}
|
||||
|
||||
void TraySetSystemTray(void *inctx, const char* label, const char* image, int isTemplate, const char* tooltip, void *inMenu) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
WailsMenu *menu = (__bridge WailsMenu*) inMenu;
|
||||
NSString *nslabel = safeInit(label);
|
||||
NSString *nsimage = safeInit(image);
|
||||
NSString *nstooltip = safeInit(tooltip);
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
if (ctx.trayItem == nil) {
|
||||
ctx.trayItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
|
||||
}
|
||||
if (nslabel != nil) {
|
||||
ctx.trayItem.button.title = nslabel;
|
||||
}
|
||||
if (nstooltip != nil) {
|
||||
ctx.trayItem.button.toolTip = nstooltip;
|
||||
}
|
||||
if (nsimage != nil) {
|
||||
if ([nsimage length] > 0) {
|
||||
NSImage *icon = [[NSImage alloc] initWithContentsOfFile:nsimage];
|
||||
if (icon == nil) {
|
||||
// Try as base64
|
||||
NSData *data = [[NSData alloc] initWithBase64EncodedString:nsimage options:NSDataBase64DecodingIgnoreUnknownCharacters];
|
||||
if (data != nil) {
|
||||
icon = [[NSImage alloc] initWithData:data];
|
||||
[data release];
|
||||
}
|
||||
}
|
||||
if (icon != nil) {
|
||||
if (isTemplate) {
|
||||
[icon setTemplate:YES];
|
||||
}
|
||||
ctx.trayItem.button.image = icon;
|
||||
ctx.trayItem.button.imagePosition = NSImageLeft;
|
||||
[icon release];
|
||||
}
|
||||
} else {
|
||||
ctx.trayItem.button.image = nil;
|
||||
}
|
||||
}
|
||||
if (menu != nil) {
|
||||
[ctx.trayItem setMenu:menu];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void SetAbout(void *inctx, const char* title, const char* description, void* imagedata, int datalen) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSString *_title = safeInit(title);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
@property (retain) WailsWebView* webview;
|
||||
@property (nonatomic, assign) id appdelegate;
|
||||
|
||||
@property bool hideOnClose;
|
||||
@property int hideOnClose;
|
||||
@property bool shuttingDown;
|
||||
@property bool startHidden;
|
||||
@property bool startFullscreen;
|
||||
|
|
@ -55,6 +55,8 @@
|
|||
|
||||
@property (retain) NSMenu* applicationMenu;
|
||||
|
||||
@property (retain) NSStatusItem* trayItem;
|
||||
|
||||
@property (retain) NSImage* aboutImage;
|
||||
@property (retain) NSString* aboutTitle;
|
||||
@property (retain) NSString* aboutDescription;
|
||||
|
|
@ -65,7 +67,7 @@ struct Preferences {
|
|||
bool *fullscreenEnabled;
|
||||
};
|
||||
|
||||
- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)zoomable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString *)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight :(bool)fraudulentWebsiteWarningEnabled :(struct Preferences)preferences :(bool)enableDragAndDrop :(bool)disableWebViewDragAndDrop;
|
||||
- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)zoomable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(int)hideWindowOnClose :(NSString *)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight :(bool)fraudulentWebsiteWarningEnabled :(struct Preferences)preferences :(bool)enableDragAndDrop :(bool)disableWebViewDragAndDrop;
|
||||
- (void) SetSize:(int)width :(int)height;
|
||||
- (void) SetPosition:(int)x :(int) y;
|
||||
- (void) SetMinSize:(int)minWidth :(int)minHeight;
|
||||
|
|
|
|||
|
|
@ -109,6 +109,10 @@ typedef void (^schemeTaskCaller)(id<WKURLSchemeTask>);
|
|||
[self.mouseEvent release];
|
||||
[self.userContentController release];
|
||||
[self.applicationMenu release];
|
||||
if (self.trayItem != nil) {
|
||||
[[NSStatusBar systemStatusBar] removeStatusItem:self.trayItem];
|
||||
[self.trayItem release];
|
||||
}
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +140,7 @@ typedef void (^schemeTaskCaller)(id<WKURLSchemeTask>);
|
|||
return NO;
|
||||
}
|
||||
|
||||
- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)zoomable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString*)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight :(bool)fraudulentWebsiteWarningEnabled :(struct Preferences)preferences :(bool)enableDragAndDrop :(bool)disableWebViewDragAndDrop {
|
||||
- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)zoomable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(int)hideWindowOnClose :(NSString*)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight :(bool)fraudulentWebsiteWarningEnabled :(struct Preferences)preferences :(bool)enableDragAndDrop :(bool)disableWebViewDragAndDrop {
|
||||
NSWindowStyleMask styleMask = 0;
|
||||
|
||||
if( !frameless ) {
|
||||
|
|
@ -397,6 +401,9 @@ typedef void (^schemeTaskCaller)(id<WKURLSchemeTask>);
|
|||
}
|
||||
|
||||
- (void) Show {
|
||||
if ([NSApp activationPolicy] == NSApplicationActivationPolicyAccessory) {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
}
|
||||
[self.mainWindow makeKeyAndOrderFront:nil];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
@interface WindowDelegate : NSObject <NSWindowDelegate>
|
||||
|
||||
@property bool hideOnClose;
|
||||
@property int hideOnClose;
|
||||
|
||||
@property (assign) WailsContext* ctx;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,15 @@
|
|||
|
||||
@implementation WindowDelegate
|
||||
- (BOOL)windowShouldClose:(WailsWindow *)sender {
|
||||
if( self.hideOnClose ) {
|
||||
if( self.hideOnClose == 1 ) {
|
||||
[NSApp hide:nil];
|
||||
return false;
|
||||
}
|
||||
if( self.hideOnClose == 2 ) {
|
||||
[sender orderOut:nil];
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
|
||||
return false;
|
||||
}
|
||||
processMessage("Q");
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
|
|
@ -76,6 +77,8 @@ type Frontend struct {
|
|||
bindings *binding.Bindings
|
||||
dispatcher frontend.Dispatcher
|
||||
|
||||
trayMenu *menu.TrayMenu
|
||||
|
||||
originValidator *originvalidator.OriginValidator
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -132,3 +132,12 @@ func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) {
|
|||
func (f *Frontend) MenuUpdateApplicationMenu() {
|
||||
f.mainWindow.UpdateApplicationMenu()
|
||||
}
|
||||
|
||||
func (f *Frontend) TraySetSystemTray(trayMenu *menu.TrayMenu) {
|
||||
if trayMenu != nil {
|
||||
f.trayMenu = trayMenu
|
||||
}
|
||||
if f.mainWindow != nil && f.trayMenu != nil {
|
||||
f.mainWindow.TraySetSystemTray(f.trayMenu)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window
|
|||
resizable := bool2Cint(!frontendOptions.DisableResize)
|
||||
fullscreen := bool2Cint(frontendOptions.Fullscreen)
|
||||
alwaysOnTop := bool2Cint(frontendOptions.AlwaysOnTop)
|
||||
hideWindowOnClose := bool2Cint(frontendOptions.HideWindowOnClose)
|
||||
startsHidden := bool2Cint(frontendOptions.StartHidden)
|
||||
devtoolsEnabled := bool2Cint(devtools)
|
||||
defaultContextMenuEnabled := bool2Cint(debug || frontendOptions.EnableDefaultContextMenu)
|
||||
|
|
@ -121,9 +120,18 @@ func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window
|
|||
|
||||
appearance = c.String(string(mac.Appearance))
|
||||
}
|
||||
|
||||
hideWindowOnClose := 0
|
||||
if frontendOptions.HideWindowOnClose {
|
||||
hideWindowOnClose = 1
|
||||
if frontendOptions.Tray != nil {
|
||||
hideWindowOnClose = 2
|
||||
}
|
||||
}
|
||||
|
||||
var context *C.WailsContext = C.Create(title, width, height, frameless, resizable, zoomable, fullscreen, fullSizeContent,
|
||||
hideTitleBar, titlebarAppearsTransparent, hideTitle, useToolbar, hideToolbarSeparator, webviewIsTransparent,
|
||||
alwaysOnTop, hideWindowOnClose, appearance, windowIsTranslucent, contentProtection, devtoolsEnabled, defaultContextMenuEnabled,
|
||||
alwaysOnTop, C.int(hideWindowOnClose), appearance, windowIsTranslucent, contentProtection, devtoolsEnabled, defaultContextMenuEnabled,
|
||||
windowStartState, startsHidden, minWidth, minHeight, maxWidth, maxHeight, enableFraudulentWebsiteWarnings,
|
||||
preferences, singleInstanceEnabled, singleInstanceUniqueId, enableDragAndDrop, disableWebViewDragAndDrop,
|
||||
)
|
||||
|
|
@ -308,6 +316,37 @@ func (w *Window) UpdateApplicationMenu() {
|
|||
C.UpdateApplicationMenu(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) TraySetSystemTray(trayMenu *menu.TrayMenu) {
|
||||
if w == nil || w.context == nil {
|
||||
return
|
||||
}
|
||||
if trayMenu == nil {
|
||||
return
|
||||
}
|
||||
label := C.CString(trayMenu.Label)
|
||||
defer C.free(unsafe.Pointer(label))
|
||||
|
||||
image := C.CString(trayMenu.Image)
|
||||
defer C.free(unsafe.Pointer(image))
|
||||
|
||||
tooltip := C.CString(trayMenu.Tooltip)
|
||||
defer C.free(unsafe.Pointer(tooltip))
|
||||
|
||||
var nsmenu unsafe.Pointer
|
||||
if trayMenu.Menu != nil {
|
||||
appMenu := NewNSMenu(w.context, "")
|
||||
processMenu(appMenu, trayMenu.Menu)
|
||||
nsmenu = appMenu.nsmenu
|
||||
}
|
||||
|
||||
isTemplate := 0
|
||||
if trayMenu.MacTemplateImage {
|
||||
isTemplate = 1
|
||||
}
|
||||
|
||||
C.TraySetSystemTray(w.context, label, image, C.int(isTemplate), tooltip, nsmenu)
|
||||
}
|
||||
|
||||
func (w Window) Print() {
|
||||
C.WindowPrint(w.context)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import (
|
|||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
var radioUpdating bool
|
||||
|
||||
func GtkMenuItemWithLabel(label string) *C.GtkWidget {
|
||||
cLabel := C.CString(label)
|
||||
result := C.gtk_menu_item_new_with_label(cLabel)
|
||||
|
|
@ -45,41 +47,64 @@ func GtkRadioMenuItemWithLabel(label string, group *C.GSList) *C.GtkWidget {
|
|||
|
||||
//export handleMenuItemClick
|
||||
func handleMenuItemClick(gtkWidget unsafe.Pointer) {
|
||||
// Make sure to execute the final callback on a new goroutine otherwise if the callback e.g. tries to open a dialog, the
|
||||
// main thread will get blocked and so the message loop blocks. As a result the app will block and shows a
|
||||
// "not responding" dialog.
|
||||
invokeOnMainThread(func() {
|
||||
// Make sure to execute the final callback on a new goroutine otherwise if the callback e.g. tries to open a dialog, the
|
||||
// main thread will get blocked and so the message loop blocks. As a result the app will block and shows a
|
||||
// "not responding" dialog.
|
||||
|
||||
item := gtkSignalToMenuItem[(*C.GtkWidget)(gtkWidget)]
|
||||
switch item.Type {
|
||||
case menu.CheckboxType:
|
||||
item.Checked = !item.Checked
|
||||
checked := C.int(0)
|
||||
if item.Checked {
|
||||
checked = C.int(1)
|
||||
widget := (*C.GtkWidget)(gtkWidget)
|
||||
item := appMenuCache.gtkSignalToMenuItem[widget]
|
||||
if item == nil {
|
||||
item = trayMenuCache.gtkSignalToMenuItem[widget]
|
||||
}
|
||||
for _, gtkCheckbox := range gtkCheckboxCache[item] {
|
||||
handler := gtkSignalHandlers[gtkCheckbox]
|
||||
C.blockClick(gtkCheckbox, handler)
|
||||
C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(gtkCheckbox)), checked)
|
||||
C.unblockClick(gtkCheckbox, handler)
|
||||
if item == nil {
|
||||
return
|
||||
}
|
||||
go item.Click(&menu.CallbackData{MenuItem: item})
|
||||
case menu.RadioType:
|
||||
gtkRadioItems := gtkRadioMenuCache[item]
|
||||
active := C.gtk_check_menu_item_get_active(C.toGtkCheckMenuItem(gtkWidget))
|
||||
if int(active) == 1 {
|
||||
for _, gtkRadioItem := range gtkRadioItems {
|
||||
handler := gtkSignalHandlers[gtkRadioItem]
|
||||
C.blockClick(gtkRadioItem, handler)
|
||||
C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(gtkRadioItem)), 1)
|
||||
C.unblockClick(gtkRadioItem, handler)
|
||||
|
||||
switch item.Type {
|
||||
case menu.CheckboxType:
|
||||
item.Checked = !item.Checked
|
||||
checked := C.int(0)
|
||||
if item.Checked {
|
||||
checked = C.int(1)
|
||||
}
|
||||
item.Checked = true
|
||||
|
||||
updateCheckbox := func(cache *menuCache) {
|
||||
for _, gtkCheckbox := range cache.gtkCheckboxCache[item] {
|
||||
handler := cache.gtkSignalHandlers[gtkCheckbox]
|
||||
C.blockClick(gtkCheckbox, handler)
|
||||
C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(gtkCheckbox)), checked)
|
||||
C.unblockClick(gtkCheckbox, handler)
|
||||
}
|
||||
}
|
||||
updateCheckbox(appMenuCache)
|
||||
updateCheckbox(trayMenuCache)
|
||||
|
||||
go item.Click(&menu.CallbackData{MenuItem: item})
|
||||
case menu.RadioType:
|
||||
active := C.gtk_check_menu_item_get_active(C.toGtkCheckMenuItem(gtkWidget))
|
||||
if int(active) == 1 {
|
||||
updateRadio := func(cache *menuCache) {
|
||||
for _, gtkRadioItem := range cache.gtkRadioMenuCache[item] {
|
||||
handler := cache.gtkSignalHandlers[gtkRadioItem]
|
||||
C.blockClick(gtkRadioItem, handler)
|
||||
C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(gtkRadioItem)), 1)
|
||||
C.unblockClick(gtkRadioItem, handler)
|
||||
}
|
||||
}
|
||||
radioUpdating = true
|
||||
updateRadio(appMenuCache)
|
||||
updateRadio(trayMenuCache)
|
||||
radioUpdating = false
|
||||
item.Checked = true
|
||||
go item.Click(&menu.CallbackData{MenuItem: item})
|
||||
} else {
|
||||
if !radioUpdating {
|
||||
item.Checked = false
|
||||
}
|
||||
}
|
||||
default:
|
||||
go item.Click(&menu.CallbackData{MenuItem: item})
|
||||
} else {
|
||||
item.Checked = false
|
||||
}
|
||||
default:
|
||||
go item.Click(&menu.CallbackData{MenuItem: item})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,11 @@ package linux
|
|||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
#include "window.h"
|
||||
|
||||
static GtkMenuItem *toGtkMenuItem(void *pointer) { return (GTK_MENU_ITEM(pointer)); }
|
||||
static GtkMenu *toGtkMenu(void *pointer) { return (GTK_MENU(pointer)); }
|
||||
static GtkWindow *toGtkWindow(void *pointer) { return (GTK_WINDOW(pointer)); }
|
||||
static GtkMenuShell *toGtkMenuShell(void *pointer) { return (GTK_MENU_SHELL(pointer)); }
|
||||
static GtkCheckMenuItem *toGtkCheckMenuItem(void *pointer) { return (GTK_CHECK_MENU_ITEM(pointer)); }
|
||||
static GtkRadioMenuItem *toGtkRadioMenuItem(void *pointer) { return (GTK_RADIO_MENU_ITEM(pointer)); }
|
||||
|
|
@ -35,19 +38,39 @@ void addAccelerator(GtkWidget* menuItem, GtkAccelGroup* group, guint key, GdkMod
|
|||
*/
|
||||
import "C"
|
||||
import (
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
var menuIdCounter int
|
||||
var menuItemToId map[*menu.MenuItem]int
|
||||
var menuIdToItem map[int]*menu.MenuItem
|
||||
var gtkCheckboxCache map[*menu.MenuItem][]*C.GtkWidget
|
||||
var gtkMenuCache map[*menu.MenuItem]*C.GtkWidget
|
||||
var gtkRadioMenuCache map[*menu.MenuItem][]*C.GtkWidget
|
||||
var gtkSignalHandlers map[*C.GtkWidget]C.gulong
|
||||
var gtkSignalToMenuItem map[*C.GtkWidget]*menu.MenuItem
|
||||
type menuCache struct {
|
||||
menuIdCounter int
|
||||
menuItemToId map[*menu.MenuItem]int
|
||||
menuIdToItem map[int]*menu.MenuItem
|
||||
gtkCheckboxCache map[*menu.MenuItem][]*C.GtkWidget
|
||||
gtkMenuCache map[*menu.MenuItem]*C.GtkWidget
|
||||
gtkRadioMenuCache map[*menu.MenuItem][]*C.GtkWidget
|
||||
gtkSignalHandlers map[*C.GtkWidget]C.gulong
|
||||
gtkSignalToMenuItem map[*C.GtkWidget]*menu.MenuItem
|
||||
}
|
||||
|
||||
func newMenuCache() *menuCache {
|
||||
return &menuCache{
|
||||
menuItemToId: make(map[*menu.MenuItem]int),
|
||||
menuIdToItem: make(map[int]*menu.MenuItem),
|
||||
gtkCheckboxCache: make(map[*menu.MenuItem][]*C.GtkWidget),
|
||||
gtkMenuCache: make(map[*menu.MenuItem]*C.GtkWidget),
|
||||
gtkRadioMenuCache: make(map[*menu.MenuItem][]*C.GtkWidget),
|
||||
gtkSignalHandlers: make(map[*C.GtkWidget]C.gulong),
|
||||
gtkSignalToMenuItem: make(map[*C.GtkWidget]*menu.MenuItem),
|
||||
}
|
||||
}
|
||||
|
||||
var appMenuCache = newMenuCache()
|
||||
var trayMenuCache = newMenuCache()
|
||||
|
||||
func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) {
|
||||
f.mainWindow.SetApplicationMenu(menu)
|
||||
|
|
@ -57,6 +80,71 @@ func (f *Frontend) MenuUpdateApplicationMenu() {
|
|||
f.mainWindow.SetApplicationMenu(f.mainWindow.applicationMenu)
|
||||
}
|
||||
|
||||
func (f *Frontend) TraySetSystemTray(trayMenu *menu.TrayMenu) {
|
||||
if trayMenu == nil {
|
||||
return
|
||||
}
|
||||
|
||||
invokeOnMainThread(func() {
|
||||
trayMenuCache = newMenuCache()
|
||||
var label *C.char
|
||||
if trayMenu.Label != "" {
|
||||
label = C.CString(trayMenu.Label)
|
||||
defer C.free(unsafe.Pointer(label))
|
||||
}
|
||||
|
||||
var tooltip *C.char
|
||||
if trayMenu.Tooltip != "" {
|
||||
tooltip = C.CString(trayMenu.Tooltip)
|
||||
defer C.free(unsafe.Pointer(tooltip))
|
||||
}
|
||||
|
||||
var imageData *C.guchar
|
||||
var imageLen C.gsize
|
||||
var imageBytes []byte
|
||||
if trayMenu.Image != "" {
|
||||
// Try file
|
||||
if _, err := os.Stat(trayMenu.Image); err == nil {
|
||||
data, err := os.ReadFile(trayMenu.Image)
|
||||
if err == nil && len(data) > 0 {
|
||||
imageBytes = data
|
||||
}
|
||||
} else {
|
||||
// Try base64
|
||||
data, err := base64.StdEncoding.DecodeString(trayMenu.Image)
|
||||
if err == nil && len(data) > 0 {
|
||||
imageBytes = data
|
||||
}
|
||||
}
|
||||
if len(imageBytes) > 0 {
|
||||
imageData = (*C.guchar)(unsafe.Pointer(&imageBytes[0]))
|
||||
imageLen = C.gsize(len(imageBytes))
|
||||
}
|
||||
}
|
||||
|
||||
if f.mainWindow.trayAccelGroup != nil {
|
||||
C.gtk_window_remove_accel_group(C.toGtkWindow(f.mainWindow.gtkWindow), f.mainWindow.trayAccelGroup)
|
||||
C.g_object_unref(C.gpointer(f.mainWindow.trayAccelGroup))
|
||||
f.mainWindow.trayAccelGroup = nil
|
||||
}
|
||||
|
||||
var gtkMenu *C.GtkWidget
|
||||
if trayMenu.Menu != nil {
|
||||
gtkMenu = C.gtk_menu_new()
|
||||
f.mainWindow.trayAccelGroup = C.gtk_accel_group_new()
|
||||
C.gtk_window_add_accel_group(C.toGtkWindow(f.mainWindow.gtkWindow), f.mainWindow.trayAccelGroup)
|
||||
C.gtk_menu_set_accel_group(C.toGtkMenu(unsafe.Pointer(gtkMenu)), f.mainWindow.trayAccelGroup)
|
||||
currentRadioGroup = nil
|
||||
for _, item := range trayMenu.Menu.Items {
|
||||
processMenuItem(gtkMenu, item, f.mainWindow.trayAccelGroup, trayMenuCache)
|
||||
}
|
||||
}
|
||||
|
||||
C.TraySetSystemTray(C.toGtkWindow(f.mainWindow.gtkWindow), label, imageData, imageLen, tooltip, gtkMenu)
|
||||
runtime.KeepAlive(imageBytes)
|
||||
})
|
||||
}
|
||||
|
||||
func (w *Window) SetApplicationMenu(inmenu *menu.Menu) {
|
||||
if inmenu == nil {
|
||||
return
|
||||
|
|
@ -66,13 +154,7 @@ func (w *Window) SetApplicationMenu(inmenu *menu.Menu) {
|
|||
w.accels = C.gtk_accel_group_new()
|
||||
C.gtk_window_add_accel_group(w.asGTKWindow(), w.accels)
|
||||
|
||||
menuItemToId = make(map[*menu.MenuItem]int)
|
||||
menuIdToItem = make(map[int]*menu.MenuItem)
|
||||
gtkCheckboxCache = make(map[*menu.MenuItem][]*C.GtkWidget)
|
||||
gtkMenuCache = make(map[*menu.MenuItem]*C.GtkWidget)
|
||||
gtkRadioMenuCache = make(map[*menu.MenuItem][]*C.GtkWidget)
|
||||
gtkSignalHandlers = make(map[*C.GtkWidget]C.gulong)
|
||||
gtkSignalToMenuItem = make(map[*C.GtkWidget]*menu.MenuItem)
|
||||
appMenuCache = newMenuCache()
|
||||
|
||||
// Increase ref count?
|
||||
w.menubar = C.gtk_menu_bar_new()
|
||||
|
|
@ -83,36 +165,37 @@ func (w *Window) SetApplicationMenu(inmenu *menu.Menu) {
|
|||
}
|
||||
|
||||
func processMenu(window *Window, menu *menu.Menu) {
|
||||
currentRadioGroup = nil
|
||||
for _, menuItem := range menu.Items {
|
||||
if menuItem.SubMenu != nil {
|
||||
submenu := processSubmenu(menuItem, window.accels)
|
||||
submenu := processSubmenu(menuItem, window.accels, appMenuCache)
|
||||
C.gtk_menu_shell_append(C.toGtkMenuShell(unsafe.Pointer(window.menubar)), submenu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processSubmenu(menuItem *menu.MenuItem, group *C.GtkAccelGroup) *C.GtkWidget {
|
||||
existingMenu := gtkMenuCache[menuItem]
|
||||
func processSubmenu(menuItem *menu.MenuItem, group *C.GtkAccelGroup, cache *menuCache) *C.GtkWidget {
|
||||
existingMenu := cache.gtkMenuCache[menuItem]
|
||||
if existingMenu != nil {
|
||||
return existingMenu
|
||||
}
|
||||
gtkMenu := C.gtk_menu_new()
|
||||
submenu := GtkMenuItemWithLabel(menuItem.Label)
|
||||
for _, menuItem := range menuItem.SubMenu.Items {
|
||||
menuID := menuIdCounter
|
||||
menuIdToItem[menuID] = menuItem
|
||||
menuItemToId[menuItem] = menuID
|
||||
menuIdCounter++
|
||||
processMenuItem(gtkMenu, menuItem, group)
|
||||
menuID := cache.menuIdCounter
|
||||
cache.menuIdToItem[menuID] = menuItem
|
||||
cache.menuItemToId[menuItem] = menuID
|
||||
cache.menuIdCounter++
|
||||
processMenuItem(gtkMenu, menuItem, group, cache)
|
||||
}
|
||||
C.gtk_menu_item_set_submenu(C.toGtkMenuItem(unsafe.Pointer(submenu)), gtkMenu)
|
||||
gtkMenuCache[menuItem] = existingMenu
|
||||
cache.gtkMenuCache[menuItem] = gtkMenu
|
||||
return submenu
|
||||
}
|
||||
|
||||
var currentRadioGroup *C.GSList
|
||||
|
||||
func processMenuItem(parent *C.GtkWidget, menuItem *menu.MenuItem, group *C.GtkAccelGroup) {
|
||||
func processMenuItem(parent *C.GtkWidget, menuItem *menu.MenuItem, group *C.GtkAccelGroup, cache *menuCache) {
|
||||
if menuItem.Hidden {
|
||||
return
|
||||
}
|
||||
|
|
@ -137,7 +220,7 @@ func processMenuItem(parent *C.GtkWidget, menuItem *menu.MenuItem, group *C.GtkA
|
|||
if menuItem.Checked {
|
||||
C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(result)), 1)
|
||||
}
|
||||
gtkCheckboxCache[menuItem] = append(gtkCheckboxCache[menuItem], result)
|
||||
cache.gtkCheckboxCache[menuItem] = append(cache.gtkCheckboxCache[menuItem], result)
|
||||
|
||||
case menu.RadioType:
|
||||
result = GtkRadioMenuItemWithLabel(menuItem.Label, currentRadioGroup)
|
||||
|
|
@ -145,24 +228,24 @@ func processMenuItem(parent *C.GtkWidget, menuItem *menu.MenuItem, group *C.GtkA
|
|||
if menuItem.Checked {
|
||||
C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(result)), 1)
|
||||
}
|
||||
gtkRadioMenuCache[menuItem] = append(gtkRadioMenuCache[menuItem], result)
|
||||
cache.gtkRadioMenuCache[menuItem] = append(cache.gtkRadioMenuCache[menuItem], result)
|
||||
case menu.SubmenuType:
|
||||
result = processSubmenu(menuItem, group)
|
||||
result = processSubmenu(menuItem, group, cache)
|
||||
}
|
||||
C.gtk_menu_shell_append(C.toGtkMenuShell(unsafe.Pointer(parent)), result)
|
||||
C.gtk_widget_show(result)
|
||||
|
||||
if menuItem.Click != nil {
|
||||
handler := C.connectClick(result)
|
||||
gtkSignalHandlers[result] = handler
|
||||
gtkSignalToMenuItem[result] = menuItem
|
||||
cache.gtkSignalHandlers[result] = handler
|
||||
cache.gtkSignalToMenuItem[result] = menuItem
|
||||
}
|
||||
|
||||
if menuItem.Disabled {
|
||||
C.gtk_widget_set_sensitive(result, 0)
|
||||
}
|
||||
|
||||
if menuItem.Accelerator != nil {
|
||||
if menuItem.Accelerator != nil && group != nil {
|
||||
key, mods := acceleratorToGTK(menuItem.Accelerator)
|
||||
C.addAccelerator(result, group, key, mods)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
#include <JavaScriptCore/JavaScript.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <libayatana-appindicator/app-indicator.h>
|
||||
#include <webkit2/webkit2.h>
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <locale.h>
|
||||
#include <unistd.h>
|
||||
#include "window.h"
|
||||
|
||||
// These are the x,y,time & button of the last mouse down event
|
||||
|
|
@ -18,7 +20,11 @@ static int wmIsWayland = -1;
|
|||
static int decoratorWidth = -1;
|
||||
static int decoratorHeight = -1;
|
||||
|
||||
// casts
|
||||
// Tray management
|
||||
static AppIndicator *indicator = NULL;
|
||||
static char *indicator_temp_icon_path = NULL;
|
||||
static GtkWidget *current_window = NULL;
|
||||
|
||||
void ExecuteOnMainThread(void *f, gpointer jscallback)
|
||||
{
|
||||
g_idle_add((GSourceFunc)f, (gpointer)jscallback);
|
||||
|
|
@ -153,7 +159,9 @@ void SetWindowIcon(GtkWindow *window, const guchar *buf, gsize len)
|
|||
{
|
||||
return;
|
||||
}
|
||||
if (gdk_pixbuf_loader_write(loader, buf, len, NULL) && gdk_pixbuf_loader_close(loader, NULL))
|
||||
gboolean write_success = gdk_pixbuf_loader_write(loader, buf, len, NULL);
|
||||
gboolean close_success = gdk_pixbuf_loader_close(loader, NULL);
|
||||
if (write_success && close_success)
|
||||
{
|
||||
GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
|
||||
if (pixbuf)
|
||||
|
|
@ -889,3 +897,99 @@ void InstallF12Hotkey(void *window)
|
|||
GClosure *closure = g_cclosure_new(G_CALLBACK(sendShowInspectorMessage), window, NULL);
|
||||
gtk_accel_group_connect(accel_group, GDK_KEY_F12, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE, closure);
|
||||
}
|
||||
|
||||
static void on_status_icon_activate(AppIndicator *indicator, gpointer user_data)
|
||||
{
|
||||
if (current_window)
|
||||
{
|
||||
gtk_window_present(GTK_WINDOW(current_window));
|
||||
}
|
||||
}
|
||||
|
||||
void TraySetSystemTray(GtkWindow *window, const char *label, const guchar *image, gsize imageLen, const char *tooltip, GtkWidget *menu)
|
||||
{
|
||||
current_window = GTK_WIDGET(window);
|
||||
|
||||
if (indicator == NULL)
|
||||
{
|
||||
indicator = app_indicator_new("wails-tray-indicator", "", APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
|
||||
if (indicator == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
app_indicator_set_status(indicator, APP_INDICATOR_STATUS_ACTIVE);
|
||||
|
||||
// Connect the secondary activate signal (usually middle click or scroll)
|
||||
// to show the window.
|
||||
g_signal_connect(indicator, "X-AYATANA-SECONDARY-ACTIVATE", G_CALLBACK(on_status_icon_activate), NULL);
|
||||
}
|
||||
|
||||
if (label != NULL)
|
||||
{
|
||||
app_indicator_set_label(indicator, label, "");
|
||||
}
|
||||
|
||||
if (image != NULL && imageLen > 0)
|
||||
{
|
||||
GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
|
||||
gboolean write_success = gdk_pixbuf_loader_write(loader, image, imageLen, NULL);
|
||||
gboolean close_success = gdk_pixbuf_loader_close(loader, NULL);
|
||||
if (write_success && close_success)
|
||||
{
|
||||
GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
|
||||
if (pixbuf)
|
||||
{
|
||||
char *filename = NULL;
|
||||
int fd = g_file_open_tmp("wails-tray-XXXXXX.png", &filename, NULL);
|
||||
if (fd != -1)
|
||||
{
|
||||
close(fd);
|
||||
if (gdk_pixbuf_save(pixbuf, filename, "png", NULL, NULL))
|
||||
{
|
||||
char *dir = g_path_get_dirname(filename);
|
||||
char *base = g_path_get_basename(filename);
|
||||
char *dot = strrchr(base, '.');
|
||||
if (dot)
|
||||
{
|
||||
*dot = '\0';
|
||||
}
|
||||
app_indicator_set_icon_theme_path(indicator, dir);
|
||||
app_indicator_set_icon_full(indicator, base, "tray icon");
|
||||
g_free(dir);
|
||||
g_free(base);
|
||||
|
||||
if (indicator_temp_icon_path != NULL)
|
||||
{
|
||||
unlink(indicator_temp_icon_path);
|
||||
g_free(indicator_temp_icon_path);
|
||||
}
|
||||
indicator_temp_icon_path = filename;
|
||||
}
|
||||
else
|
||||
{
|
||||
unlink(filename);
|
||||
g_free(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
g_object_unref(loader);
|
||||
}
|
||||
|
||||
if (tooltip != NULL)
|
||||
{
|
||||
app_indicator_set_title(indicator, tooltip);
|
||||
}
|
||||
|
||||
static GtkWidget *prev_menu = NULL;
|
||||
if (menu != NULL)
|
||||
{
|
||||
if (prev_menu != NULL)
|
||||
{
|
||||
gtk_widget_destroy(prev_menu);
|
||||
}
|
||||
app_indicator_set_menu(indicator, GTK_MENU(menu));
|
||||
prev_menu = menu;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@
|
|||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo linux pkg-config: gtk+-3.0 ayatana-appindicator3-0.1
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
#include <JavaScriptCore/JavaScript.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <libayatana-appindicator/app-indicator.h>
|
||||
#include <webkit2/webkit2.h>
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
|
|
@ -49,6 +50,7 @@ type Window struct {
|
|||
webviewBox *C.GtkWidget
|
||||
vbox *C.GtkWidget
|
||||
accels *C.GtkAccelGroup
|
||||
trayAccelGroup *C.GtkAccelGroup
|
||||
minWidth, minHeight, maxWidth, maxHeight int
|
||||
}
|
||||
|
||||
|
|
@ -98,10 +100,18 @@ func NewWindow(appoptions *options.App, debug bool, devtoolsEnabled bool) *Windo
|
|||
webviewGpuPolicy = int(linux.WebviewGpuPolicyNever)
|
||||
}
|
||||
|
||||
hideWindowOnClose := 0
|
||||
if appoptions.HideWindowOnClose {
|
||||
hideWindowOnClose = 1
|
||||
if appoptions.Tray != nil {
|
||||
hideWindowOnClose = 2
|
||||
}
|
||||
}
|
||||
|
||||
webview := C.SetupWebview(
|
||||
result.contentManager,
|
||||
result.asGTKWindow(),
|
||||
bool2Cint(appoptions.HideWindowOnClose),
|
||||
C.int(hideWindowOnClose),
|
||||
C.int(webviewGpuPolicy),
|
||||
bool2Cint(appoptions.DragAndDrop != nil && appoptions.DragAndDrop.DisableWebViewDrop),
|
||||
bool2Cint(appoptions.DragAndDrop != nil && appoptions.DragAndDrop.EnableFileDrop),
|
||||
|
|
@ -180,6 +190,10 @@ func (w *Window) UnFullscreen() {
|
|||
func (w *Window) Destroy() {
|
||||
C.gtk_widget_destroy(w.asGTKWidget())
|
||||
C.g_object_unref(C.gpointer(w.gtkWindow))
|
||||
if w.trayAccelGroup != nil {
|
||||
C.g_object_unref(C.gpointer(w.trayAccelGroup))
|
||||
w.trayAccelGroup = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) Close() {
|
||||
|
|
|
|||
|
|
@ -125,4 +125,6 @@ void sendShowInspectorMessage();
|
|||
void ShowInspector(void *webview);
|
||||
void InstallF12Hotkey(void *window);
|
||||
|
||||
void TraySetSystemTray(GtkWindow *window, const char *label, const guchar *image, gsize imageLen, const char *tooltip, GtkWidget *menu);
|
||||
|
||||
#endif /* window_h */
|
||||
|
|
|
|||
|
|
@ -130,3 +130,9 @@ func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) {
|
|||
func (f *Frontend) MenuUpdateApplicationMenu() {
|
||||
processMenu(f.mainWindow, f.mainWindow.applicationMenu)
|
||||
}
|
||||
|
||||
func (f *Frontend) TraySetSystemTray(trayMenu *menu.TrayMenu) {
|
||||
if f.mainWindow != nil {
|
||||
f.mainWindow.TraySetSystemTray(trayMenu)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,35 @@ import (
|
|||
type HRESULT int32
|
||||
type HANDLE uintptr
|
||||
type HMONITOR HANDLE
|
||||
type HWND HANDLE
|
||||
type HICON HANDLE
|
||||
|
||||
type NOTIFYICONDATA struct {
|
||||
CbSize uint32
|
||||
HWnd HWND
|
||||
UID uint32
|
||||
UFlags uint32
|
||||
UCallbackMessage uint32
|
||||
HIcon HICON
|
||||
SzTip [128]uint16
|
||||
DwState uint32
|
||||
DwStateMask uint32
|
||||
SzInfo [256]uint16
|
||||
UVersion uint32
|
||||
SzInfoTitle [64]uint16
|
||||
DwInfoFlags uint32
|
||||
GuidItem [16]byte
|
||||
}
|
||||
|
||||
const (
|
||||
NIM_ADD = 0x00000000
|
||||
NIM_MODIFY = 0x00000001
|
||||
NIM_DELETE = 0x00000002
|
||||
|
||||
NIF_MESSAGE = 0x00000001
|
||||
NIF_ICON = 0x00000002
|
||||
NIF_TIP = 0x00000004
|
||||
)
|
||||
|
||||
var (
|
||||
moduser32 = syscall.NewLazyDLL("user32.dll")
|
||||
|
|
@ -29,12 +58,17 @@ var (
|
|||
procEmptyClipboard = moduser32.NewProc("EmptyClipboard")
|
||||
procGetClipboardData = moduser32.NewProc("GetClipboardData")
|
||||
procSetClipboardData = moduser32.NewProc("SetClipboardData")
|
||||
procCreateIconFromResourceEx = moduser32.NewProc("CreateIconFromResourceEx")
|
||||
)
|
||||
var (
|
||||
moddwmapi = syscall.NewLazyDLL("dwmapi.dll")
|
||||
procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute")
|
||||
procDwmExtendFrameIntoClientArea = moddwmapi.NewProc("DwmExtendFrameIntoClientArea")
|
||||
)
|
||||
var (
|
||||
modshell32 = syscall.NewLazyDLL("shell32.dll")
|
||||
procShellNotifyIcon = modshell32.NewProc("Shell_NotifyIconW")
|
||||
)
|
||||
var (
|
||||
modwingdi = syscall.NewLazyDLL("gdi32.dll")
|
||||
procCreateSolidBrush = modwingdi.NewProc("CreateSolidBrush")
|
||||
|
|
|
|||
|
|
@ -100,7 +100,27 @@ func IsVisible(hwnd uintptr) bool {
|
|||
ret, _, _ := procIsWindowVisible.Call(hwnd)
|
||||
return ret != 0
|
||||
}
|
||||
func ShellNotifyIcon(dwMessage uintptr, lpdata *NOTIFYICONDATA) bool {
|
||||
ret, _, _ := procShellNotifyIcon.Call(dwMessage, uintptr(unsafe.Pointer(lpdata)))
|
||||
return ret != 0
|
||||
}
|
||||
|
||||
func CreateIconFromResourceEx(presbits uintptr, dwResSize uint32, fIcon bool, dwVer uint32, cxDesired int, cyDesired int) HICON {
|
||||
var fIconUint uintptr
|
||||
if fIcon {
|
||||
fIconUint = 1
|
||||
}
|
||||
ret, _, _ := procCreateIconFromResourceEx.Call(
|
||||
presbits,
|
||||
uintptr(dwResSize),
|
||||
fIconUint,
|
||||
uintptr(dwVer),
|
||||
uintptr(cxDesired),
|
||||
uintptr(cyDesired),
|
||||
0,
|
||||
)
|
||||
return HICON(ret)
|
||||
}
|
||||
func IsWindowFullScreen(hwnd uintptr) bool {
|
||||
wRect := GetWindowRect(hwnd)
|
||||
m := MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY)
|
||||
|
|
|
|||
|
|
@ -205,6 +205,10 @@ func (mi *MenuItem) OnClick() *EventManager {
|
|||
return &mi.onClick
|
||||
}
|
||||
|
||||
func (mi *MenuItem) Handle() w32.HMENU {
|
||||
return mi.hMenu
|
||||
}
|
||||
|
||||
func (mi *MenuItem) AddSeparator() {
|
||||
addMenuItem(mi.hSubMenu, 0, "-", Shortcut{}, nil, false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -820,7 +820,7 @@ func CreatePopupMenu() HMENU {
|
|||
}
|
||||
|
||||
func TrackPopupMenuEx(hMenu HMENU, fuFlags uint32, x, y int32, hWnd HWND, lptpm *TPMPARAMS) BOOL {
|
||||
ret, _, _ := syscall.Syscall6(trackPopupMenuEx, 6,
|
||||
ret, _, _ := syscall.SyscallN(trackPopupMenuEx,
|
||||
uintptr(hMenu),
|
||||
uintptr(fuFlags),
|
||||
uintptr(x),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@ import (
|
|||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32"
|
||||
"github.com/wailsapp/wails/v2/internal/system/operatingsystem"
|
||||
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
|
|
@ -18,6 +23,10 @@ import (
|
|||
winoptions "github.com/wailsapp/wails/v2/pkg/options/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
WM_WAILS_TRAY_MESSAGE = w32.WM_APP + 500
|
||||
)
|
||||
|
||||
type Window struct {
|
||||
winc.Form
|
||||
frontendOptions *options.App
|
||||
|
|
@ -39,6 +48,11 @@ type Window struct {
|
|||
|
||||
chromium *edge.Chromium
|
||||
|
||||
// Tray
|
||||
trayID uint32
|
||||
trayIcon w32.HICON
|
||||
trayMenu *menu.TrayMenu
|
||||
|
||||
// isMinimizing indicates whether the window is currently being minimized
|
||||
// 标识窗口是否处于最小化状态,用于解决最小化/恢复时的闪屏问题
|
||||
// This flag is used to prevent unnecessary redraws during minimize/restore transitions for frameless windows
|
||||
|
|
@ -203,6 +217,21 @@ func (w *Window) IsVisible() bool {
|
|||
func (w *Window) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||
|
||||
switch msg {
|
||||
case WM_WAILS_TRAY_MESSAGE:
|
||||
switch uint32(lparam) {
|
||||
case w32.WM_RBUTTONUP:
|
||||
if w.trayMenu != nil && w.trayMenu.Menu != nil {
|
||||
w.showTrayMenu()
|
||||
}
|
||||
case w32.WM_LBUTTONUP:
|
||||
w32.ShowWindow(w.Handle(), w32.SW_SHOW)
|
||||
w32.SetForegroundWindow(w.Handle())
|
||||
}
|
||||
return 0
|
||||
case w32.WM_DESTROY:
|
||||
if w.trayIcon != 0 {
|
||||
w.deleteTrayIcon()
|
||||
}
|
||||
case win32.WM_POWERBROADCAST:
|
||||
switch wparam {
|
||||
case win32.PBT_APMSUSPEND:
|
||||
|
|
@ -317,6 +346,118 @@ func (w *Window) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
|||
return w.Form.WndProc(msg, wparam, lparam)
|
||||
}
|
||||
|
||||
func (w *Window) showTrayMenu() {
|
||||
if w.trayMenu == nil || w.trayMenu.Menu == nil {
|
||||
return
|
||||
}
|
||||
|
||||
wincMenu := winc.NewContextMenu()
|
||||
defer w32.DestroyMenu(w32.HMENU(wincMenu.Handle()))
|
||||
for _, item := range w.trayMenu.Menu.Items {
|
||||
processMenuItem(wincMenu, item)
|
||||
}
|
||||
|
||||
x, y, _ := w32.GetCursorPos()
|
||||
|
||||
w32.SetForegroundWindow(w.Handle())
|
||||
w32.TrackPopupMenuEx(w32.HMENU(wincMenu.Handle()), w32.TPM_LEFTALIGN|w32.TPM_RIGHTBUTTON, int32(x), int32(y), w.Handle(), nil)
|
||||
w32.PostMessage(w.Handle(), w32.WM_NULL, 0, 0)
|
||||
}
|
||||
|
||||
func (w *Window) deleteTrayIcon() {
|
||||
if w.trayIcon == 0 {
|
||||
return
|
||||
}
|
||||
var nid win32.NOTIFYICONDATA
|
||||
nid.CbSize = uint32(unsafe.Sizeof(nid))
|
||||
nid.HWnd = win32.HWND(w.Handle())
|
||||
nid.UID = w.trayID
|
||||
|
||||
win32.ShellNotifyIcon(win32.NIM_DELETE, &nid)
|
||||
w32.DestroyIcon(w.trayIcon)
|
||||
w.trayIcon = 0
|
||||
w.trayID = 0
|
||||
}
|
||||
|
||||
func (w *Window) TraySetSystemTray(trayMenu *menu.TrayMenu) {
|
||||
if trayMenu == nil {
|
||||
return
|
||||
}
|
||||
w.Invoke(func() {
|
||||
w.trayMenu = trayMenu
|
||||
|
||||
var nid win32.NOTIFYICONDATA
|
||||
nid.CbSize = uint32(unsafe.Sizeof(nid))
|
||||
nid.HWnd = win32.HWND(w.Handle())
|
||||
nid.UID = w.trayID
|
||||
nid.UFlags = win32.NIF_MESSAGE | win32.NIF_TIP
|
||||
nid.UCallbackMessage = WM_WAILS_TRAY_MESSAGE
|
||||
|
||||
if trayMenu.Tooltip != "" {
|
||||
u16, _ := syscall.UTF16FromString(trayMenu.Tooltip)
|
||||
copy(nid.SzTip[:], u16)
|
||||
}
|
||||
|
||||
if trayMenu.Image != "" {
|
||||
icon, err := w.loadTrayIcon(trayMenu.Image)
|
||||
if err == nil {
|
||||
if w.trayIcon != 0 {
|
||||
w32.DestroyIcon(w.trayIcon)
|
||||
}
|
||||
w.trayIcon = icon
|
||||
nid.HIcon = win32.HICON(icon)
|
||||
nid.UFlags |= win32.NIF_ICON
|
||||
} else {
|
||||
if w.frontendOptions.Logger != nil {
|
||||
w.frontendOptions.Logger.Error(fmt.Sprintf("Could not load tray icon: %s", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmd := win32.NIM_ADD
|
||||
if w.trayID != 0 {
|
||||
cmd = win32.NIM_MODIFY
|
||||
} else {
|
||||
w.trayID = 1
|
||||
nid.UID = w.trayID
|
||||
}
|
||||
|
||||
if !win32.ShellNotifyIcon(uintptr(cmd), &nid) {
|
||||
if w.frontendOptions.Logger != nil {
|
||||
w.frontendOptions.Logger.Error(fmt.Sprintf("Could not set system tray icon (cmd=%d, id=%d)", cmd, nid.UID))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *Window) loadTrayIcon(image string) (w32.HICON, error) {
|
||||
if _, err := os.Stat(image); err == nil {
|
||||
ico, err := winc.NewIconFromFile(image)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to load icon from file %q: %w", image, err)
|
||||
}
|
||||
return w32.HICON(ico.Handle()), nil
|
||||
}
|
||||
|
||||
data, err := base64.StdEncoding.DecodeString(image)
|
||||
if err == nil {
|
||||
return w.createIconFromBytes(data)
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("could not load icon")
|
||||
}
|
||||
|
||||
func (w *Window) createIconFromBytes(data []byte) (w32.HICON, error) {
|
||||
if len(data) == 0 {
|
||||
return 0, fmt.Errorf("empty icon data")
|
||||
}
|
||||
icon := win32.CreateIconFromResourceEx(uintptr(unsafe.Pointer(&data[0])), uint32(len(data)), true, 0x00030000, 0, 0)
|
||||
if icon == 0 {
|
||||
return 0, fmt.Errorf("could not create icon from bytes")
|
||||
}
|
||||
return w32.HICON(icon), nil
|
||||
}
|
||||
|
||||
func (w *Window) IsMaximised() bool {
|
||||
return win32.IsWindowMaximised(w.Handle())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ type Frontend interface {
|
|||
// Menus
|
||||
MenuSetApplicationMenu(menu *menu.Menu)
|
||||
MenuUpdateApplicationMenu()
|
||||
TraySetSystemTray(trayMenu *menu.TrayMenu)
|
||||
|
||||
// Events
|
||||
Notify(name string, data ...interface{})
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package cfd
|
|||
import "errors"
|
||||
|
||||
var (
|
||||
ErrCancelled = errors.New("cancelled by user")
|
||||
ErrInvalidGUID = errors.New("guid cannot be nil")
|
||||
ErrCancelled = errors.New("cancelled by user")
|
||||
ErrInvalidGUID = errors.New("guid cannot be nil")
|
||||
ErrEmptyFilters = errors.New("must specify at least one filter")
|
||||
)
|
||||
|
|
|
|||
45
v2/internal/menumanager/traymenu_test.go
Normal file
45
v2/internal/menumanager/traymenu_test.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package menumanager
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
func TestNewTrayMenu(t *testing.T) {
|
||||
tm := &menu.TrayMenu{
|
||||
Label: "Test Label",
|
||||
Tooltip: "Test Tooltip",
|
||||
Disabled: false,
|
||||
}
|
||||
|
||||
tray := NewTrayMenu(tm)
|
||||
|
||||
if tray.Label != tm.Label {
|
||||
t.Errorf("Expected label %s, got %s", tm.Label, tray.Label)
|
||||
}
|
||||
|
||||
if tray.Tooltip != tm.Tooltip {
|
||||
t.Errorf("Expected tooltip %s, got %s", tm.Tooltip, tray.Tooltip)
|
||||
}
|
||||
|
||||
if tray.Disabled != tm.Disabled {
|
||||
t.Errorf("Expected disabled %v, got %v", tm.Disabled, tray.Disabled)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTrayMenuWithANSI(t *testing.T) {
|
||||
tm := &menu.TrayMenu{
|
||||
Label: "\033[31mRed Label\033[0m",
|
||||
}
|
||||
|
||||
tray := NewTrayMenu(tm)
|
||||
|
||||
if tray.Label != tm.Label {
|
||||
t.Errorf("Expected label %s, got %s", tm.Label, tray.Label)
|
||||
}
|
||||
|
||||
if len(tray.StyledLabel) == 0 {
|
||||
t.Error("Expected StyledLabel to be populated for ANSI text")
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"unsafe"
|
||||
)
|
||||
|
||||
func CreateIconFromResourceEx(presbits uintptr, dwResSize uint32, isIcon bool, version uint32, cxDesired int, cyDesired int, flags uint) (uintptr, error) {
|
||||
func CreateIconFromResourceEx(presbits uintptr, dwResSize uint32, isIcon bool, version uint32, cxDesired int, cyDesired int, flags uint) (HICON, error) {
|
||||
icon := 0
|
||||
if isIcon {
|
||||
icon = 1
|
||||
|
|
@ -24,12 +24,12 @@ func CreateIconFromResourceEx(presbits uintptr, dwResSize uint32, isIcon bool, v
|
|||
if r == 0 {
|
||||
return 0, err
|
||||
}
|
||||
return r, nil
|
||||
return HICON(r), nil
|
||||
}
|
||||
|
||||
// CreateHIconFromPNG creates a HICON from a PNG file
|
||||
func CreateHIconFromPNG(pngData []byte) (HICON, error) {
|
||||
icon, err := CreateIconFromResourceEx(
|
||||
return CreateIconFromResourceEx(
|
||||
uintptr(unsafe.Pointer(&pngData[0])),
|
||||
uint32(len(pngData)),
|
||||
true,
|
||||
|
|
@ -37,5 +37,4 @@ func CreateHIconFromPNG(pngData []byte) (HICON, error) {
|
|||
0,
|
||||
0,
|
||||
LR_DEFAULTSIZE)
|
||||
return HICON(icon), err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
//go:build linux && (webkit2_36 || webkit2_40 || webkit2_41 )
|
||||
//go:build linux && (webkit2_36 || webkit2_40 || webkit2_41)
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0 libsoup-2.4
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1 libsoup-3.0
|
||||
|
||||
|
|
|
|||
|
|
@ -32,17 +32,19 @@ type Experimental struct{}
|
|||
|
||||
// App contains options for creating the App
|
||||
type App struct {
|
||||
Title string
|
||||
Width int
|
||||
Height int
|
||||
DisableResize bool
|
||||
Fullscreen bool
|
||||
Frameless bool
|
||||
MinWidth int
|
||||
MinHeight int
|
||||
MaxWidth int
|
||||
MaxHeight int
|
||||
StartHidden bool
|
||||
Title string
|
||||
Width int
|
||||
Height int
|
||||
DisableResize bool
|
||||
Fullscreen bool
|
||||
Frameless bool
|
||||
MinWidth int
|
||||
MinHeight int
|
||||
MaxWidth int
|
||||
MaxHeight int
|
||||
StartHidden bool
|
||||
// HideWindowOnClose controls close button behavior.
|
||||
// If true and a tray icon is configured, close behaves like HideWindowAndDock.
|
||||
HideWindowOnClose bool
|
||||
AlwaysOnTop bool
|
||||
// BackgroundColour is the background colour of the window
|
||||
|
|
@ -55,6 +57,7 @@ type App struct {
|
|||
// AssetServer configures the Assets for the application
|
||||
AssetServer *assetserver.Options
|
||||
Menu *menu.Menu
|
||||
Tray *menu.TrayMenu
|
||||
Logger logger.Logger `json:"-"`
|
||||
LogLevel logger.LogLevel
|
||||
LogLevelProduction logger.LogLevel
|
||||
|
|
|
|||
13
v2/pkg/runtime/tray.go
Normal file
13
v2/pkg/runtime/tray.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
// TraySetSystemTray sets the system tray menu
|
||||
func TraySetSystemTray(ctx context.Context, trayMenu *menu.TrayMenu) {
|
||||
frontend := getFrontend(ctx)
|
||||
frontend.TraySetSystemTray(trayMenu)
|
||||
}
|
||||
95
v2/pkg/runtime/tray_test.go
Normal file
95
v2/pkg/runtime/tray_test.go
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
type mockFrontend struct {
|
||||
frontend.Frontend
|
||||
trayMenu *menu.TrayMenu
|
||||
}
|
||||
|
||||
func (m *mockFrontend) TraySetSystemTray(trayMenu *menu.TrayMenu) {
|
||||
m.trayMenu = trayMenu
|
||||
}
|
||||
|
||||
func (m *mockFrontend) Run(ctx context.Context) error { return nil }
|
||||
func (m *mockFrontend) RunMainLoop() {}
|
||||
func (m *mockFrontend) ExecJS(js string) {}
|
||||
func (m *mockFrontend) Hide() {}
|
||||
func (m *mockFrontend) Show() {}
|
||||
func (m *mockFrontend) Quit() {}
|
||||
|
||||
func (m *mockFrontend) OpenFileDialog(dialogOptions frontend.OpenDialogOptions) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
func (m *mockFrontend) OpenMultipleFilesDialog(dialogOptions frontend.OpenDialogOptions) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockFrontend) OpenDirectoryDialog(dialogOptions frontend.OpenDialogOptions) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
func (m *mockFrontend) SaveFileDialog(dialogOptions frontend.SaveDialogOptions) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
func (m *mockFrontend) MessageDialog(dialogOptions frontend.MessageDialogOptions) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (m *mockFrontend) WindowSetTitle(title string) {}
|
||||
func (m *mockFrontend) WindowShow() {}
|
||||
func (m *mockFrontend) WindowHide() {}
|
||||
func (m *mockFrontend) WindowCenter() {}
|
||||
func (m *mockFrontend) WindowToggleMaximise() {}
|
||||
func (m *mockFrontend) WindowMaximise() {}
|
||||
func (m *mockFrontend) WindowUnmaximise() {}
|
||||
func (m *mockFrontend) WindowMinimise() {}
|
||||
func (m *mockFrontend) WindowUnminimise() {}
|
||||
func (m *mockFrontend) WindowSetAlwaysOnTop(b bool) {}
|
||||
func (m *mockFrontend) WindowSetPosition(x int, y int) {}
|
||||
func (m *mockFrontend) WindowGetPosition() (int, int) { return 0, 0 }
|
||||
func (m *mockFrontend) WindowSetSize(width int, height int) {}
|
||||
func (m *mockFrontend) WindowGetSize() (int, int) { return 0, 0 }
|
||||
func (m *mockFrontend) WindowSetMinSize(width int, height int) {}
|
||||
func (m *mockFrontend) WindowSetMaxSize(width int, height int) {}
|
||||
func (m *mockFrontend) WindowFullscreen() {}
|
||||
func (m *mockFrontend) WindowUnfullscreen() {}
|
||||
func (m *mockFrontend) WindowSetBackgroundColour(col *options.RGBA) {}
|
||||
func (m *mockFrontend) WindowReload() {}
|
||||
func (m *mockFrontend) WindowReloadApp() {}
|
||||
func (m *mockFrontend) WindowSetSystemDefaultTheme() {}
|
||||
func (m *mockFrontend) WindowSetLightTheme() {}
|
||||
func (m *mockFrontend) WindowSetDarkTheme() {}
|
||||
func (m *mockFrontend) WindowIsMaximised() bool { return false }
|
||||
func (m *mockFrontend) WindowIsMinimised() bool { return false }
|
||||
func (m *mockFrontend) WindowIsNormal() bool { return false }
|
||||
func (m *mockFrontend) WindowIsFullscreen() bool { return false }
|
||||
func (m *mockFrontend) WindowClose() {}
|
||||
func (m *mockFrontend) WindowPrint() {}
|
||||
func (m *mockFrontend) ScreenGetAll() ([]frontend.Screen, error) { return nil, nil }
|
||||
func (m *mockFrontend) MenuSetApplicationMenu(menu *menu.Menu) {}
|
||||
func (m *mockFrontend) MenuUpdateApplicationMenu() {}
|
||||
func (m *mockFrontend) Notify(name string, data ...interface{}) {}
|
||||
func (m *mockFrontend) BrowserOpenURL(url string) {}
|
||||
func (m *mockFrontend) ClipboardGetText() (string, error) { return "", nil }
|
||||
func (m *mockFrontend) ClipboardSetText(text string) error { return nil }
|
||||
|
||||
func TestTraySetSystemTray(t *testing.T) {
|
||||
mock := &mockFrontend{}
|
||||
ctx := context.WithValue(context.Background(), "frontend", mock)
|
||||
|
||||
trayMenu := &menu.TrayMenu{
|
||||
Label: "Test",
|
||||
}
|
||||
|
||||
TraySetSystemTray(ctx, trayMenu)
|
||||
|
||||
if mock.trayMenu != trayMenu {
|
||||
t.Errorf("Expected tray menu to be set")
|
||||
}
|
||||
}
|
||||
|
|
@ -42,6 +42,7 @@ func main() {
|
|||
Middleware: assetsMidldeware,
|
||||
},
|
||||
Menu: app.applicationMenu(),
|
||||
Tray: app.trayMenu(),
|
||||
Logger: nil,
|
||||
LogLevel: logger.DEBUG,
|
||||
LogLevelProduction: logger.ERROR,
|
||||
|
|
@ -248,6 +249,19 @@ hide the window instead.
|
|||
Name: HideWindowOnClose<br/>
|
||||
Type: `bool`
|
||||
|
||||
### WindowCloseBehaviour
|
||||
|
||||
This defines the behavior of the window when it is closed.
|
||||
|
||||
| Value | Description |
|
||||
| :--- | :--- |
|
||||
| `options.CloseWindow` | Closes the window and the application (Default) |
|
||||
| `options.HideWindow` | Hides the window instead of closing it |
|
||||
| `options.HideWindowAndDock` | (macOS only) Hides the window and the app stays in the dock |
|
||||
|
||||
Name: WindowCloseBehaviour<br/>
|
||||
Type: `options.WindowCloseBehaviour`
|
||||
|
||||
### BackgroundColour
|
||||
|
||||
This value is the default background colour of the window.
|
||||
|
|
@ -365,6 +379,13 @@ On Mac, if no menu is specified, a default menu will be created.
|
|||
Name: Menu<br/>
|
||||
Type: `*menu.Menu`
|
||||
|
||||
### Tray
|
||||
|
||||
The tray menu to be used by the application. More details about Tray in the [Tray Reference](../reference/runtime/tray.mdx).
|
||||
|
||||
Name: Tray<br/>
|
||||
Type: `*menu.TrayMenu`
|
||||
|
||||
### Logger
|
||||
|
||||
The logger to be used by the application. More details about logging in the [Log Reference](../reference/runtime/log.mdx).
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ It has utility methods for:
|
|||
- [Browser](browser.mdx)
|
||||
- [Log](log.mdx)
|
||||
- [Clipboard](clipboard.mdx)
|
||||
- [Tray](tray.mdx)
|
||||
|
||||
The Go Runtime is available through importing `github.com/wailsapp/wails/v2/pkg/runtime`. All methods in this package
|
||||
take a context as the first parameter. This context should be obtained from the [OnStartup](../options.mdx#onstartup)
|
||||
|
|
|
|||
37
website/docs/reference/runtime/tray.mdx
Normal file
37
website/docs/reference/runtime/tray.mdx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
sidebar_position: 11
|
||||
---
|
||||
|
||||
# Tray
|
||||
|
||||
These methods give control of the system tray.
|
||||
|
||||
### TraySetSystemTray
|
||||
|
||||
Sets the system tray menu.
|
||||
|
||||
Go: `TraySetSystemTray(ctx context.Context, trayMenu *menu.TrayMenu)`
|
||||
|
||||
#### TrayMenu
|
||||
|
||||
The `TrayMenu` struct defines the system tray icon and its associated menu.
|
||||
|
||||
| Field | Type | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| Label | string | The text to display in the tray (platform dependent) |
|
||||
| Image | string | The name of the tray icon or base64 image data |
|
||||
| MacTemplateImage | bool | (macOS only) Indicates if the image is a template image |
|
||||
| Tooltip | string | The tooltip text to show when hovering over the tray icon |
|
||||
| Menu | [*Menu](./menu.mdx) | The menu to display when the tray icon is clicked |
|
||||
|
||||
:::info Windows
|
||||
On Windows, the icon is displayed in the notification area.
|
||||
:::
|
||||
|
||||
:::info macOS
|
||||
On macOS, the icon is displayed in the menu bar. If `MacTemplateImage` is true, the icon will automatically adapt to light/dark mode.
|
||||
:::
|
||||
|
||||
:::info Linux
|
||||
On Linux, support depends on the desktop environment and its support for system tray icons (StatusNotifierItem).
|
||||
:::
|
||||
|
|
@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Added system tray runtime wrapper and platform implementations by [@SomeoneIsWorking](https://github.com/SomeoneIsWorking) in [PR](https://github.com/wailsapp/wails/pull/4991)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `wails init` to prevent initialization in non-empty directories when using the `-d` flag, avoiding accidental data loss [`#4940`](https://github.com/wailsapp/wails/issues/4940) by `@leaanthony`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue