mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-03 22:33:46 +08:00
[v3] Late service registration and error handling overhaul (#4066)
* Add service registration method * Fix error handling and formatting in messageprocessor * Add configurable error handling * Improve error strings * Fix service shutdown on macOS * Add post shutdown hook * Better fatal errors * Add startup/shutdown sequence tests * Improve debug messages * Update JS runtime * Update docs * Update changelog * Fix log message in clipboard message processor Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Remove panic in RegisterService * Fix linux tests (hopefully) * Fix error formatting everywhere * Fix typo in windows webview * Tidy example mods * Set application name in tests * Fix ubuntu test workflow * Cleanup template test pipeline * Fix dev build detection on Go 1.24 * Update template go.mod/sum to Go 1.24 * Remove redundant caching in template tests * Final format string cleanup * Fix wails3 tool references * Fix legacy log calls * Remove formatJS and simplify format strings * Fix indirect import --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
parent
5059adc561
commit
e7c134de4e
87
.github/workflows/build-and-test-v3.yml
vendored
87
.github/workflows/build-and-test-v3.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest, ubuntu-latest]
|
||||
go-version: [1.23]
|
||||
go-version: [1.24]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@ -40,7 +40,7 @@ jobs:
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
with:
|
||||
packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config
|
||||
packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config xvfb x11-xserver-utils at-spi2-core xdg-desktop-portal-gtk
|
||||
version: 1.0
|
||||
|
||||
- name: Setup Go
|
||||
@ -66,11 +66,25 @@ jobs:
|
||||
working-directory: ./v3
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Run tests (!mac)
|
||||
if: matrix.os != 'macos-latest'
|
||||
- name: Run tests (windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
working-directory: ./v3
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Run tests (ubuntu)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
working-directory: ./v3
|
||||
run: >
|
||||
xvfb-run --auto-servernum
|
||||
sh -c '
|
||||
dbus-update-activation-environment --systemd --all &&
|
||||
go test -v ./...
|
||||
'
|
||||
|
||||
- name: Typecheck binding generator output
|
||||
working-directory: ./v3
|
||||
run: task generator:test:check
|
||||
|
||||
test_js:
|
||||
name: Run JS Tests
|
||||
needs: check_approval
|
||||
@ -105,41 +119,23 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
template:
|
||||
[
|
||||
svelte,
|
||||
svelte-ts,
|
||||
vue,
|
||||
vue-ts,
|
||||
react,
|
||||
react-ts,
|
||||
preact,
|
||||
preact-ts,
|
||||
lit,
|
||||
lit-ts,
|
||||
vanilla,
|
||||
vanilla-ts,
|
||||
]
|
||||
go-version: [1.23]
|
||||
- svelte
|
||||
- svelte-ts
|
||||
- vue
|
||||
- vue-ts
|
||||
- react
|
||||
- react-ts
|
||||
- preact
|
||||
- preact-ts
|
||||
- lit
|
||||
- lit-ts
|
||||
- vanilla
|
||||
- vanilla-ts
|
||||
go-version: [1.24]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
cache-dependency-path: "v3/go.sum"
|
||||
|
||||
- name: Setup Golang caches
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-golang-
|
||||
|
||||
- name: Install linux dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
@ -147,17 +143,28 @@ jobs:
|
||||
packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config
|
||||
version: 1.0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
cache-dependency-path: "v3/go.sum"
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v2
|
||||
with:
|
||||
version: 3.x
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Wails3 CLI
|
||||
working-directory: ./v3
|
||||
run: |
|
||||
cd ./v3/cmd/wails3
|
||||
go install
|
||||
wails3 -help
|
||||
task install
|
||||
wails3 doctor
|
||||
|
||||
- name: Generate template '${{ matrix.template }}'
|
||||
run: |
|
||||
go install github.com/go-task/task/v3/cmd/task@latest
|
||||
mkdir -p ./test-${{ matrix.template }}
|
||||
cd ./test-${{ matrix.template }}
|
||||
wails3 init -n ${{ matrix.template }} -t ${{ matrix.template }}
|
||||
cd ${{ matrix.template }}
|
||||
wails3 build
|
||||
wails3 build
|
||||
|
@ -54,6 +54,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Add `//wails:internal` directive on services and models to allow for types that are exported in Go but not in JS/TS by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Add binding generator support for constants of alias type to allow for weakly typed enums by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Add support for macOS 15 "Sequoia" to `OSInfo.Branding` for improved OS version detection in [#4065](https://github.com/wailsapp/wails/pull/4065)
|
||||
- Add `PostShutdown` hook for running custom code after the shutdown process completes by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Add `FatalError` struct to support detection of fatal errors in custom error handlers by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Standardise and document service startup and shutdown order by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Add test harness for application startup/shutdown sequence and service startup/shutdown tests by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Add `RegisterService` method for registering services after the application has been created by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Add `MarshalError` field in application and service options for custom error handling in binding calls by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
|
||||
### Fixed
|
||||
|
||||
@ -81,6 +87,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Suppressed warnings for services that define lifecycle or http methods but no other bound methods by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Fixed non-React templates failing to display Hello World footer when using light system colour scheme by [@marcus-crane](https://github.com/marcus-crane) in [#4056](https://github.com/wailsapp/wails/pull/4056)
|
||||
- Fixed hidden menu items on macOS by [@leaanthony](https://github.com/leaanthony)
|
||||
- Fixed handling and formatting of errors in message processors by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Fixed skipped service shutdown when quitting application by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
|
||||
### Changed
|
||||
|
||||
@ -98,6 +106,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Update copyright date to 2025 by [@IanVS](https://github.com/IanVS) in [#4037](https://github.com/wailsapp/wails/pull/4037)
|
||||
- Add docs for event.Sender by [@IanVS](https://github.com/IanVS) in [#4075](https://github.com/wailsapp/wails/pull/4075)
|
||||
- Go 1.24 support by [@leaanthony](https://github.com/leaanthony)
|
||||
- `ServiceStartup` hooks are now invoked when `App.Run` is called, not in `application.New` by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- `ServiceStartup` errors are now returned from `App.Run` instead of terminating the process by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Binding and dialog calls from JS now reject with error objects instead of strings by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
|
||||
## v3.0.0-alpha.9 - 2025-01-13
|
||||
|
||||
|
@ -448,3 +448,149 @@ const promise = MyService.LongRunningTask("input");
|
||||
// This will cause the context to be cancelled in the Go method
|
||||
promise.cancel();
|
||||
```
|
||||
|
||||
### Handling errors
|
||||
|
||||
As you may have noticed above, bound methods can return errors, which are handled specially.
|
||||
When a result field has type `error`, it is omitted by default from the values returned to JS.
|
||||
When such a field is _non-nil_, the promise rejects with a `RuntimeError` exception
|
||||
that wraps the Go error message:
|
||||
|
||||
```go
|
||||
func (*MyService) FailingMethod(name string) error {
|
||||
return fmt.Errorf("Welcome to an imperfect world, %s", name)
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
import { MyService } from './bindings/changeme';
|
||||
|
||||
try {
|
||||
await MyService.FailingMethod("CLU")
|
||||
} catch (err) {
|
||||
if (err.name === 'RuntimeError') {
|
||||
console.log(err.message); // Prints 'Welcome to an imperfect world, CLU'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The exception will be an instance of the `Call.RuntimeError` class from the wails runtime,
|
||||
hence you can also test its type like this:
|
||||
|
||||
```js
|
||||
import { Call } from '@wailsio/runtime';
|
||||
|
||||
try {
|
||||
// ...
|
||||
} catch (err) {
|
||||
if (err instanceof Call.RuntimeError) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the Go error value supports JSON marshaling, the exception's `cause` property
|
||||
will hold the marshaled version of the error:
|
||||
|
||||
```go
|
||||
type ImperfectWorldError struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (err *ImperfectWorldError) Error() {
|
||||
return fmt.Sprintf("Welcome to an imperfect world, %s", err.Name)
|
||||
}
|
||||
|
||||
func (*MyService) FailingMethod(name string) error {
|
||||
return &ImperfectWorldError{
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
import { MyService } from './bindings/changeme';
|
||||
|
||||
try {
|
||||
await MyService.FailingMethod("CLU")
|
||||
} catch (err) {
|
||||
if (err.name === 'RuntimeError') {
|
||||
console.log(err.cause.name); // Prints 'CLU'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Generally, many Go error values will only have limited or no support for marshaling to JSON.
|
||||
If you so wish, you can customise the value provided as cause
|
||||
by specifying either a global or per-service error marshaling function:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
MarshalError: func(err error) []byte {
|
||||
// ...
|
||||
},
|
||||
Services: []application.Service{
|
||||
application.NewServiceWithOptions(&MyService{}, application.ServiceOptions{
|
||||
MarshalError: func(err error) []byte {
|
||||
// ...
|
||||
},
|
||||
}),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Per-service functions override the global function,
|
||||
which in turn overrides the default behaviour of using `json.Marshal`.
|
||||
If a marshaling function returns `nil`, it falls back to the outer function:
|
||||
per-service functions fall back to the global function,
|
||||
which in turn falls back to the default behaviour.
|
||||
|
||||
:::tip
|
||||
If you wish to omit the `cause` property on the resulting exception,
|
||||
let the marshaling function return a falsy JSON value like `[]byte("null")`.
|
||||
:::
|
||||
|
||||
Here's an example marshaling function that unwraps path errors and reports the file path:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
MarshalError: func(err error) []byte {
|
||||
var perr *fs.PathError
|
||||
if !errors.As(err, &perr) {
|
||||
// Not a path error, fall back to default handling.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal path string
|
||||
path, err := json.Marshal(&perr.Path)
|
||||
if err != nil {
|
||||
// String marshaling failed, fall back to default handling.
|
||||
return nil
|
||||
}
|
||||
|
||||
return []byte(fmt.Sprintf(`{"path":%s}`, path))
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
:::note
|
||||
Error marshaling functions are not allowed to fail.
|
||||
If they are not able to process a given error and return valid JSON,
|
||||
they should return `nil` and fall back to a more generic handler.
|
||||
If no strategy succeeds, the exception will not have a `cause` property.
|
||||
:::
|
||||
|
||||
Binding call promises may also reject with a `TypeError`
|
||||
when the method has been passed the wrong number of arguments,
|
||||
when the conversion of arguments from JSON to their Go types fails,
|
||||
or when the conversion of results to JSON fails.
|
||||
These problems will usually be caught early by the type system.
|
||||
If your code typechecks but you still get type errors,
|
||||
it might be that some of your Go types are not supported by the `encoding/json` package:
|
||||
look for warnings from the binding generator to catch these.
|
||||
|
||||
:::caution
|
||||
If you see a `ReferenceError` complaining about unknown methods,
|
||||
it could mean that your JS bindings have gotten out of sync with Go code
|
||||
and must be regenerated.
|
||||
:::
|
@ -40,9 +40,9 @@ greeting.
|
||||
## Registering a Service
|
||||
|
||||
To register a service with the application, you need to provide an instance of
|
||||
the service to the `Services` field of the `application.Options` struct (All
|
||||
services need to be wrapped by an `application.NewService` call. Here's an
|
||||
example:
|
||||
the service to the `Services` field of the `application.Options` struct.
|
||||
All services need to be wrapped by an `application.NewService` call.
|
||||
Here's an example:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
@ -70,6 +70,25 @@ ServiceOptions has the following fields:
|
||||
- Name - Specify a custom name for the Service
|
||||
- Route - A route to bind the Service to the frontend (more on this below)
|
||||
|
||||
After the application has been created but not yet started,
|
||||
you can register more services using the `RegisterService` method.
|
||||
This is useful when you need to feed a service some value
|
||||
that is only available after the application has been created.
|
||||
For example, let's wire application's logger into your own service:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{})
|
||||
|
||||
app.RegisterService(application.NewService(NewMyService(app.Logger)))
|
||||
|
||||
// ...
|
||||
|
||||
err := app.Run()
|
||||
```
|
||||
|
||||
Services may only be registered before running the application:
|
||||
`RegisterService` will panic if called after the `Run` method.
|
||||
|
||||
## Optional Methods
|
||||
|
||||
Services can implement optional methods to hook into the application lifecycle.
|
||||
@ -98,8 +117,12 @@ func (s *Service) ServiceStartup(ctx context.Context, options application.Servic
|
||||
|
||||
This method is called when the application is starting up. You can use it to
|
||||
initialize resources, set up connections, or perform any necessary setup tasks.
|
||||
The context is the application context, and the `options` parameter provides
|
||||
additional information about the service.
|
||||
The context is the application context that will be canceled upon shutdown,
|
||||
and the `options` parameter provides additional information about the service.
|
||||
|
||||
Services are initialised in the exact order of registration:
|
||||
first those listed in the `Services` field of the `application.Options` struct,
|
||||
then those added through the `RegisterService` method.
|
||||
|
||||
### ServiceShutdown
|
||||
|
||||
@ -110,6 +133,9 @@ func (s *Service) ServiceShutdown() error
|
||||
This method is called when the application is shutting down. Use it to clean up
|
||||
resources, close connections, or perform any necessary cleanup tasks.
|
||||
|
||||
Services are shut down in reverse registration order.
|
||||
The application context will be canceled before `ServiceShutdown` is called.
|
||||
|
||||
### ServeHTTP
|
||||
|
||||
```go
|
||||
|
@ -1,17 +1,17 @@
|
||||
module changeme
|
||||
|
||||
go 1.23.4
|
||||
go 1.24.0
|
||||
|
||||
require github.com/wailsapp/wails/v3 v3.0.0-alpha.0
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/bep/debounce v1.2.1 // indirect
|
||||
github.com/ebitengine/purego v0.4.0-alpha.4 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
github.com/go-git/go-git/v5 v5.13.1 // indirect
|
||||
github.com/go-git/go-git/v5 v5.13.2 // 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
|
||||
@ -22,26 +22,26 @@ require (
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||
github.com/leaanthony/u v1.1.0 // indirect
|
||||
github.com/lmittmann/tint v1.0.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/leaanthony/u v1.1.1 // indirect
|
||||
github.com/lmittmann/tint v1.0.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/samber/lo v1.38.1 // indirect
|
||||
github.com/samber/lo v1.49.1 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.19 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/crypto v0.33.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
|
@ -18,6 +18,7 @@ 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/ebitengine/purego v0.4.0-alpha.4 h1:Y7yIV06Yo5M2BAdD7EVPhfp6LZ0tEcQo5770OhYUVes=
|
||||
github.com/ebitengine/purego v0.4.0-alpha.4/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
|
||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
@ -39,6 +40,7 @@ github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GW
|
||||
github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw=
|
||||
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
|
||||
github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc=
|
||||
github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
@ -76,14 +78,17 @@ github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
||||
github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI=
|
||||
github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||
github.com/lmittmann/tint v1.0.0 h1:fzEj70K1L58uyoePQxKe+ezDZJ5pybiWGdA0JeFvvyw=
|
||||
github.com/lmittmann/tint v1.0.0/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/lmittmann/tint v1.0.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
github.com/matryer/is v1.4.0/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-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
@ -97,6 +102,7 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@ -114,6 +120,7 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
@ -145,9 +152,11 @@ golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
@ -157,6 +166,7 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -180,6 +190,7 @@ golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
|
@ -1,21 +1,21 @@
|
||||
module changeme
|
||||
|
||||
go 1.23.4
|
||||
go 1.24.0
|
||||
|
||||
require github.com/wailsapp/wails/v3 v3.0.0-alpha.7
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.4 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.5 // indirect
|
||||
github.com/bep/debounce v1.2.1 // indirect
|
||||
github.com/cloudflare/circl v1.5.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.0 // indirect
|
||||
github.com/ebitengine/purego v0.4.0-alpha.4 // indirect
|
||||
github.com/cloudflare/circl v1.6.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
github.com/go-git/go-git/v5 v5.13.1 // indirect
|
||||
github.com/go-git/go-git/v5 v5.13.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
@ -24,26 +24,26 @@ require (
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||
github.com/leaanthony/u v1.1.0 // indirect
|
||||
github.com/lmittmann/tint v1.0.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/leaanthony/u v1.1.1 // indirect
|
||||
github.com/lmittmann/tint v1.0.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.1 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/samber/lo v1.38.1 // indirect
|
||||
github.com/samber/lo v1.49.1 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.0 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.19 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/tools v0.29.0 // indirect
|
||||
golang.org/x/crypto v0.33.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
|
||||
golang.org/x/mod v0.23.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/tools v0.30.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
|
@ -8,6 +8,7 @@ github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/ProtonMail/go-crypto v1.1.4/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
@ -19,14 +20,17 @@ github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUK
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/cyphar/filepath-securejoin v0.4.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/ebitengine/purego v0.4.0-alpha.4 h1:Y7yIV06Yo5M2BAdD7EVPhfp6LZ0tEcQo5770OhYUVes=
|
||||
github.com/ebitengine/purego v0.4.0-alpha.4/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
|
||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
@ -43,6 +47,7 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod
|
||||
github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
|
||||
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
|
||||
github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc=
|
||||
github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
@ -73,12 +78,15 @@ github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
||||
github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI=
|
||||
github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||
github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc=
|
||||
github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
github.com/matryer/is v1.4.0/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-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
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=
|
||||
@ -87,8 +95,10 @@ github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3ev
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pjbgf/sha1cd v0.3.1/go.mod h1:Y8t7jSB/dEI/lQE04A1HVKteqjj9bX5O4+Cex0TCu8s=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
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=
|
||||
@ -100,6 +110,7 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
@ -107,6 +118,7 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
|
||||
github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ=
|
||||
github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
@ -128,14 +140,17 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
@ -147,12 +162,14 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -174,6 +191,7 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
@ -197,6 +215,7 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
||||
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -19,12 +19,15 @@ type RuntimeHandler interface {
|
||||
HandleRuntimeCall(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
type service struct {
|
||||
Route string
|
||||
Handler http.Handler
|
||||
}
|
||||
|
||||
type AssetServer struct {
|
||||
options *Options
|
||||
|
||||
handler http.Handler
|
||||
|
||||
services map[string]http.Handler
|
||||
options *Options
|
||||
handler http.Handler
|
||||
services []service
|
||||
|
||||
assetServerWebView
|
||||
}
|
||||
@ -112,11 +115,11 @@ func (a *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request, userH
|
||||
userHandler.ServeHTTP(wrapped, req)
|
||||
|
||||
default:
|
||||
// Check if the path matches the keys in the services map
|
||||
for route, handler := range a.services {
|
||||
if strings.HasPrefix(reqPath, route) {
|
||||
req.URL.Path = strings.TrimPrefix(reqPath, route)
|
||||
handler.ServeHTTP(rw, req)
|
||||
// Check if the path matches a service route
|
||||
for _, svc := range a.services {
|
||||
if strings.HasPrefix(reqPath, svc.Route) {
|
||||
req.URL.Path = strings.TrimPrefix(reqPath, svc.Route)
|
||||
svc.Handler.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -126,11 +129,8 @@ func (a *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request, userH
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AssetServer) AttachServiceHandler(prefix string, handler http.Handler) {
|
||||
if a.services == nil {
|
||||
a.services = make(map[string]http.Handler)
|
||||
}
|
||||
a.services[prefix] = handler
|
||||
func (a *AssetServer) AttachServiceHandler(route string, handler http.Handler) {
|
||||
a.services = append(a.services, service{route, handler})
|
||||
}
|
||||
|
||||
func (a *AssetServer) writeBlob(rw http.ResponseWriter, filename string, blob []byte) {
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -2,8 +2,10 @@ package buildinfo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/samber/lo"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type Info struct {
|
||||
@ -29,7 +31,9 @@ func Get() (*Info, error) {
|
||||
return setting.Key, setting.Value
|
||||
})
|
||||
result.Version = BuildInfo.Main.Version
|
||||
result.Development = BuildInfo.Main.Version == "(devel)"
|
||||
result.Development = -1 != slices.IndexFunc(BuildInfo.Settings, func(setting debug.BuildSetting) bool {
|
||||
return setting.Key == "vcs" && setting.Value == "git"
|
||||
})
|
||||
|
||||
return &result, nil
|
||||
|
||||
|
@ -2,14 +2,15 @@ package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/wailsapp/wails/v3/internal/debug"
|
||||
"github.com/wailsapp/wails/v3/internal/github"
|
||||
"github.com/wailsapp/wails/v3/internal/term"
|
||||
"github.com/wailsapp/wails/v3/internal/version"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type UpdateCLIOptions struct {
|
||||
@ -31,9 +32,9 @@ func UpdateCLI(options *UpdateCLIOptions) error {
|
||||
v3Path := filepath.ToSlash(debug.LocalModulePath + "/v3")
|
||||
term.Println("This Wails CLI has been installed from source. To update to the latest stable release, run the following commands in the `" + v3Path + "` directory:")
|
||||
term.Println(" - git pull")
|
||||
term.Println(" - go install")
|
||||
term.Println(" - wails3 task install")
|
||||
term.Println("")
|
||||
term.Println("If you want to install the latest release, please run `wails update cli -latest`")
|
||||
term.Println("If you want to install the latest release, please run `wails3 update cli -latest`")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -69,7 +70,7 @@ func UpdateCLI(options *UpdateCLIOptions) error {
|
||||
if err != nil {
|
||||
pterm.Println("")
|
||||
pterm.Println("No stable release found for this major version. To update to the latest pre-release (eg beta), run:")
|
||||
pterm.Println(" wails update -pre")
|
||||
pterm.Println(" wails3 update cli -pre")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -142,7 +143,7 @@ func updateToVersion(targetVersion *github.SemanticVersion, force bool, currentV
|
||||
// Compare
|
||||
if !success {
|
||||
pterm.Println("Error: The requested version is lower than the current version.")
|
||||
pterm.Printf("If this is what you really want to do, use `wails update -version %s`\n", targetVersionString)
|
||||
pterm.Printf("If this is what you really want to do, use `wails3 update cli -version %s`\n", targetVersionString)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -220,7 +220,7 @@ func (info *ServiceInfo) collectMethod(method *types.Func) *ServiceMethodInfo {
|
||||
}
|
||||
|
||||
fqn := path + "." + obj.Name() + "." + method.Name()
|
||||
id, _ := hash.Fnv(fqn)
|
||||
id := hash.Fnv(fqn)
|
||||
|
||||
methodInfo := &ServiceMethodInfo{
|
||||
MethodInfo: collector.Method(method).Collect(),
|
||||
@ -265,6 +265,7 @@ func (info *ServiceInfo) collectMethod(method *types.Func) *ServiceMethodInfo {
|
||||
idValue,
|
||||
)
|
||||
methodInfo.ID = strconv.FormatUint(idValue, 10)
|
||||
methodIdFound = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,8 @@ package hash
|
||||
|
||||
import "hash/fnv"
|
||||
|
||||
func Fnv(s string) (uint32, error) {
|
||||
func Fnv(s string) uint32 {
|
||||
h := fnv.New32a()
|
||||
_, err := h.Write([]byte(s))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return h.Sum32(), nil
|
||||
_, _ = h.Write([]byte(s)) // Hash implementations never return errors (see https://pkg.go.dev/hash#Hash)
|
||||
return h.Sum32()
|
||||
}
|
||||
|
@ -12,10 +12,12 @@ tasks:
|
||||
- npm install
|
||||
|
||||
test:
|
||||
dir: desktop/@wailsio/runtime
|
||||
cmds:
|
||||
- npx vitest run
|
||||
|
||||
update:
|
||||
dir: desktop/@wailsio/runtime
|
||||
cmds:
|
||||
- npx npm-check-updates -u
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@wailsio/runtime",
|
||||
"version": "3.0.0-alpha.39",
|
||||
"version": "3.0.0-alpha.55",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@wailsio/runtime",
|
||||
"version": "3.0.0-alpha.39",
|
||||
"version": "3.0.0-alpha.55",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"rimraf": "^5.0.5",
|
||||
|
@ -48,7 +48,17 @@ function generateID() {
|
||||
function resultHandler(id, data, isJSON) {
|
||||
const promiseHandler = getAndDeleteResponse(id);
|
||||
if (promiseHandler) {
|
||||
promiseHandler.resolve(isJSON ? JSON.parse(data) : data);
|
||||
if (!data) {
|
||||
promiseHandler.resolve();
|
||||
} else if (!isJSON) {
|
||||
promiseHandler.resolve(data);
|
||||
} else {
|
||||
try {
|
||||
promiseHandler.resolve(JSON.parse(data));
|
||||
} catch (err) {
|
||||
promiseHandler.reject(new TypeError("could not parse result: " + err.message, { cause: err }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,14 +66,48 @@ function resultHandler(id, data, isJSON) {
|
||||
* Handles the error from a call request.
|
||||
*
|
||||
* @param {string} id - The id of the promise handler.
|
||||
* @param {string} message - The error message to reject the promise handler with.
|
||||
* @param {string} data - The error data to reject the promise handler with.
|
||||
* @param {boolean} isJSON - Indicates whether the data is JSON or not.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
function errorHandler(id, message) {
|
||||
function errorHandler(id, data, isJSON) {
|
||||
const promiseHandler = getAndDeleteResponse(id);
|
||||
if (promiseHandler) {
|
||||
promiseHandler.reject(message);
|
||||
if (!isJSON) {
|
||||
promiseHandler.reject(new Error(data));
|
||||
} else {
|
||||
let error;
|
||||
try {
|
||||
error = JSON.parse(data);
|
||||
} catch (err) {
|
||||
promiseHandler.reject(new TypeError("could not parse error: " + err.message, { cause: err }));
|
||||
return;
|
||||
}
|
||||
|
||||
let options = {};
|
||||
if (error.cause) {
|
||||
options.cause = error.cause;
|
||||
}
|
||||
|
||||
let exception;
|
||||
switch (error.kind) {
|
||||
case "ReferenceError":
|
||||
exception = new ReferenceError(error.message, options);
|
||||
break;
|
||||
case "TypeError":
|
||||
exception = new TypeError(error.message, options);
|
||||
break;
|
||||
case "RuntimeError":
|
||||
exception = new RuntimeError(error.message, options);
|
||||
break;
|
||||
default:
|
||||
exception = new Error(error.message, options);
|
||||
break;
|
||||
}
|
||||
|
||||
promiseHandler.reject(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,30 +125,59 @@ function getAndDeleteResponse(id) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a call using the provided type and options.
|
||||
* Collects all required information for a binding call.
|
||||
*
|
||||
* @param {string|number} type - The type of call to execute.
|
||||
* @param {Object} [options={}] - Additional options for the call.
|
||||
* @return {Promise} - A promise that will be resolved or rejected based on the result of the call. It also has a cancel method to cancel a long running request.
|
||||
* @typedef {Object} CallOptions
|
||||
* @property {number} [methodID] - The numeric ID of the bound method to call.
|
||||
* @property {string} [methodName] - The fully qualified name of the bound method to call.
|
||||
* @property {any[]} args - Arguments to be passed into the bound method.
|
||||
*/
|
||||
function callBinding(type, options = {}) {
|
||||
|
||||
/**
|
||||
* Exception class that will be thrown in case the bound method returns an error.
|
||||
* The value of the {@link RuntimeError#name} property is "RuntimeError".
|
||||
*/
|
||||
export class RuntimeError extends Error {
|
||||
/**
|
||||
* Constructs a new RuntimeError instance.
|
||||
*
|
||||
* @param {string} message - The error message.
|
||||
* @param {any[]} args - Optional arguments for the Error constructor.
|
||||
*/
|
||||
constructor(message, ...args) {
|
||||
super(message, ...args);
|
||||
this.name = "RuntimeError";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a bound method according to the given call options.
|
||||
*
|
||||
* In case of failure, the returned promise will reject with an exception
|
||||
* among ReferenceError (unknown method), TypeError (wrong argument count or type),
|
||||
* {@link RuntimeError} (method returned an error), or other (network or internal errors).
|
||||
* The exception might have a "cause" field with the value returned
|
||||
* by the application- or service-level error marshaling functions.
|
||||
*
|
||||
* @param {CallOptions} options - A method call descriptor.
|
||||
* @returns {Promise<any>} - The result of the call.
|
||||
*/
|
||||
export function Call(options) {
|
||||
const id = generateID();
|
||||
const doCancel = () => { return cancelCall(type, {"call-id": id}) };
|
||||
let queuedCancel = false, callRunning = false;
|
||||
let p = new Promise((resolve, reject) => {
|
||||
options["call-id"] = id;
|
||||
callResponses.set(id, { resolve, reject });
|
||||
call(type, options).
|
||||
then((_) => {
|
||||
callRunning = true;
|
||||
if (queuedCancel) {
|
||||
return doCancel();
|
||||
}
|
||||
}).
|
||||
catch((error) => {
|
||||
reject(error);
|
||||
callResponses.delete(id);
|
||||
});
|
||||
call(CallBinding, options).then((_) => {
|
||||
callRunning = true;
|
||||
if (queuedCancel) {
|
||||
return doCancel();
|
||||
}
|
||||
}).catch((error) => {
|
||||
reject(error);
|
||||
callResponses.delete(id);
|
||||
});
|
||||
});
|
||||
p.cancel = () => {
|
||||
if (callRunning) {
|
||||
@ -118,57 +191,31 @@ function callBinding(type, options = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Call method.
|
||||
*
|
||||
* @param {Object} options - The options for the method.
|
||||
* @returns {Object} - The result of the call.
|
||||
*/
|
||||
export function Call(options) {
|
||||
return callBinding(CallBinding, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a method by name.
|
||||
* Calls a bound method by name with the specified arguments.
|
||||
* See {@link Call} for details.
|
||||
*
|
||||
* @param {string} methodName - The name of the method in the format 'package.struct.method'.
|
||||
* @param {...*} args - The arguments to pass to the method.
|
||||
* @throws {Error} If the name is not a string or is not in the correct format.
|
||||
* @returns {*} The result of the method execution.
|
||||
* @param {any[]} args - The arguments to pass to the method.
|
||||
* @returns {Promise<any>} The result of the method call.
|
||||
*/
|
||||
export function ByName(methodName, ...args) {
|
||||
return callBinding(CallBinding, {
|
||||
return Call({
|
||||
methodName,
|
||||
args
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a method by its ID with the specified arguments.
|
||||
* Calls a method by its numeric ID with the specified arguments.
|
||||
* See {@link Call} for details.
|
||||
*
|
||||
* @param {number} methodID - The ID of the method to call.
|
||||
* @param {...*} args - The arguments to pass to the method.
|
||||
* @return {*} - The result of the method call.
|
||||
* @param {any[]} args - The arguments to pass to the method.
|
||||
* @return {Promise<any>} - The result of the method call.
|
||||
*/
|
||||
export function ByID(methodID, ...args) {
|
||||
return callBinding(CallBinding, {
|
||||
return Call({
|
||||
methodID,
|
||||
args
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a method on a plugin.
|
||||
*
|
||||
* @param {string} pluginName - The name of the plugin.
|
||||
* @param {string} methodName - The name of the method to call.
|
||||
* @param {...*} args - The arguments to pass to the method.
|
||||
* @returns {*} - The result of the method call.
|
||||
*/
|
||||
export function Plugin(pluginName, methodName, ...args) {
|
||||
return callBinding(CallBinding, {
|
||||
packageName: "wails-plugins",
|
||||
structName: pluginName,
|
||||
methodName,
|
||||
args
|
||||
});
|
||||
}
|
||||
|
@ -135,12 +135,12 @@ function dialog(type, options = {}) {
|
||||
function dialogResultCallback(id, data, isJSON) {
|
||||
let p = dialogResponses.get(id);
|
||||
if (p) {
|
||||
dialogResponses.delete(id);
|
||||
if (isJSON) {
|
||||
p.resolve(JSON.parse(data));
|
||||
} else {
|
||||
p.resolve(data);
|
||||
}
|
||||
dialogResponses.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,8 +155,8 @@ function dialogResultCallback(id, data, isJSON) {
|
||||
function dialogErrorCallback(id, message) {
|
||||
let p = dialogResponses.get(id);
|
||||
if (p) {
|
||||
p.reject(message);
|
||||
dialogResponses.delete(id);
|
||||
p.reject(new Error(message));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ export function newRuntimeCaller(object, windowName) {
|
||||
/**
|
||||
* Creates a new runtime caller with specified ID.
|
||||
*
|
||||
* @param {object} object - The object to invoke the method on.
|
||||
* @param {number} object - The object to invoke the method on.
|
||||
* @param {string} windowName - The name of the window.
|
||||
* @return {Function} - The new runtime caller function.
|
||||
*/
|
||||
@ -57,8 +57,15 @@ export function newRuntimeCallerWithID(object, windowName) {
|
||||
|
||||
|
||||
function runtimeCall(method, windowName, args) {
|
||||
return runtimeCallWithID(null, method, windowName, args);
|
||||
}
|
||||
|
||||
async function runtimeCallWithID(objectID, method, windowName, args) {
|
||||
let url = new URL(runtimeURL);
|
||||
if( method ) {
|
||||
if (objectID != null) {
|
||||
url.searchParams.append("object", objectID);
|
||||
}
|
||||
if (method != null) {
|
||||
url.searchParams.append("method", method);
|
||||
}
|
||||
let fetchOptions = {
|
||||
@ -72,52 +79,14 @@ function runtimeCall(method, windowName, args) {
|
||||
}
|
||||
fetchOptions.headers["x-wails-client-id"] = clientId;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(url, fetchOptions)
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
// check content type
|
||||
if (response.headers.get("Content-Type") && response.headers.get("Content-Type").indexOf("application/json") !== -1) {
|
||||
return response.json();
|
||||
} else {
|
||||
return response.text();
|
||||
}
|
||||
}
|
||||
reject(Error(response.statusText));
|
||||
})
|
||||
.then(data => resolve(data))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
let response = await fetch(url, fetchOptions);
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
|
||||
function runtimeCallWithID(objectID, method, windowName, args) {
|
||||
let url = new URL(runtimeURL);
|
||||
url.searchParams.append("object", objectID);
|
||||
url.searchParams.append("method", method);
|
||||
let fetchOptions = {
|
||||
headers: {},
|
||||
};
|
||||
if (windowName) {
|
||||
fetchOptions.headers["x-wails-window-name"] = windowName;
|
||||
if (response.headers.get("Content-Type") && response.headers.get("Content-Type").indexOf("application/json") !== -1) {
|
||||
return response.json();
|
||||
} else {
|
||||
return response.text();
|
||||
}
|
||||
if (args) {
|
||||
url.searchParams.append("args", JSON.stringify(args));
|
||||
}
|
||||
fetchOptions.headers["x-wails-client-id"] = clientId;
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(url, fetchOptions)
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
// check content type
|
||||
if (response.headers.get("Content-Type") && response.headers.get("Content-Type").indexOf("application/json") !== -1) {
|
||||
return response.json();
|
||||
} else {
|
||||
return response.text();
|
||||
}
|
||||
}
|
||||
reject(Error(response.statusText));
|
||||
})
|
||||
.then(data => resolve(data))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
@ -1,33 +1,69 @@
|
||||
/**
|
||||
* Call method.
|
||||
* Call a bound method according to the given call options.
|
||||
*
|
||||
* @param {Object} options - The options for the method.
|
||||
* @returns {Object} - The result of the call.
|
||||
* In case of failure, the returned promise will reject with an exception
|
||||
* among ReferenceError (unknown method), TypeError (wrong argument count or type),
|
||||
* {@link RuntimeError} (method returned an error), or other (network or internal errors).
|
||||
* The exception might have a "cause" field with the value returned
|
||||
* by the application- or service-level error marshaling functions.
|
||||
*
|
||||
* @param {CallOptions} options - A method call descriptor.
|
||||
* @returns {Promise<any>} - The result of the call.
|
||||
*/
|
||||
export function Call(options: any): any;
|
||||
export function Call(options: CallOptions): Promise<any>;
|
||||
/**
|
||||
* Executes a method by name.
|
||||
* Calls a bound method by name with the specified arguments.
|
||||
* See {@link Call} for details.
|
||||
*
|
||||
* @param {string} methodName - The name of the method in the format 'package.struct.method'.
|
||||
* @param {...*} args - The arguments to pass to the method.
|
||||
* @throws {Error} If the name is not a string or is not in the correct format.
|
||||
* @returns {*} The result of the method execution.
|
||||
* @param {any[]} args - The arguments to pass to the method.
|
||||
* @returns {Promise<any>} The result of the method call.
|
||||
*/
|
||||
export function ByName(methodName: string, ...args: any[]): any;
|
||||
export function ByName(methodName: string, ...args: any[]): Promise<any>;
|
||||
/**
|
||||
* Calls a method by its ID with the specified arguments.
|
||||
* Calls a method by its numeric ID with the specified arguments.
|
||||
* See {@link Call} for details.
|
||||
*
|
||||
* @param {number} methodID - The ID of the method to call.
|
||||
* @param {...*} args - The arguments to pass to the method.
|
||||
* @return {*} - The result of the method call.
|
||||
* @param {any[]} args - The arguments to pass to the method.
|
||||
* @return {Promise<any>} - The result of the method call.
|
||||
*/
|
||||
export function ByID(methodID: number, ...args: any[]): any;
|
||||
export function ByID(methodID: number, ...args: any[]): Promise<any>;
|
||||
/**
|
||||
* Calls a method on a plugin.
|
||||
* Collects all required information for a binding call.
|
||||
*
|
||||
* @param {string} pluginName - The name of the plugin.
|
||||
* @param {string} methodName - The name of the method to call.
|
||||
* @param {...*} args - The arguments to pass to the method.
|
||||
* @returns {*} - The result of the method call.
|
||||
* @typedef {Object} CallOptions
|
||||
* @property {number} [methodID] - The numeric ID of the bound method to call.
|
||||
* @property {string} [methodName] - The fully qualified name of the bound method to call.
|
||||
* @property {any[]} args - Arguments to be passed into the bound method.
|
||||
*/
|
||||
export function Plugin(pluginName: string, methodName: string, ...args: any[]): any;
|
||||
/**
|
||||
* Exception class that will be thrown in case the bound method returns an error.
|
||||
* The value of the {@link RuntimeError#name} property is "RuntimeError".
|
||||
*/
|
||||
export class RuntimeError extends Error {
|
||||
/**
|
||||
* Constructs a new RuntimeError instance.
|
||||
*
|
||||
* @param {string} message - The error message.
|
||||
* @param {any[]} args - Optional arguments for the Error constructor.
|
||||
*/
|
||||
constructor(message: string, ...args: any[]);
|
||||
}
|
||||
/**
|
||||
* Collects all required information for a binding call.
|
||||
*/
|
||||
export type CallOptions = {
|
||||
/**
|
||||
* - The numeric ID of the bound method to call.
|
||||
*/
|
||||
methodID?: number;
|
||||
/**
|
||||
* - The fully qualified name of the bound method to call.
|
||||
*/
|
||||
methodName?: string;
|
||||
/**
|
||||
* - Arguments to be passed into the bound method.
|
||||
*/
|
||||
args: any[];
|
||||
};
|
||||
|
@ -9,11 +9,11 @@ export function newRuntimeCaller(object: any, windowName: string): Function;
|
||||
/**
|
||||
* Creates a new runtime caller with specified ID.
|
||||
*
|
||||
* @param {object} object - The object to invoke the method on.
|
||||
* @param {number} object - The object to invoke the method on.
|
||||
* @param {string} windowName - The name of the window.
|
||||
* @return {Function} - The new runtime caller function.
|
||||
*/
|
||||
export function newRuntimeCallerWithID(object: object, windowName: string): Function;
|
||||
export function newRuntimeCallerWithID(object: number, windowName: string): Function;
|
||||
export namespace objectNames {
|
||||
let Call: number;
|
||||
let Clipboard: number;
|
||||
|
@ -1,19 +1,51 @@
|
||||
module changeme
|
||||
|
||||
go 1.21
|
||||
go 1.24
|
||||
|
||||
require github.com/wailsapp/wails/v3 {{.WailsVersion}}
|
||||
|
||||
require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/leaanthony/slicer v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.5 // indirect
|
||||
github.com/adrg/xdg v0.5.3 // indirect
|
||||
github.com/bep/debounce v1.2.1 // indirect
|
||||
github.com/cloudflare/circl v1.6.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
github.com/go-git/go-git/v5 v5.13.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/uuid v1.6.0 // 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
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||
github.com/leaanthony/u v1.1.1 // indirect
|
||||
github.com/lmittmann/tint v1.0.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // 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/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.19 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // 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
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
{{if .LocalModulePath}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
{{end}}
|
||||
|
@ -1,33 +1,146 @@
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
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/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
|
||||
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM=
|
||||
github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
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.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/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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/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=
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
||||
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||
github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y=
|
||||
github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
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.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
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/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
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/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
|
||||
github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA=
|
||||
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/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
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/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU=
|
||||
github.com/wailsapp/go-webview2 v1.0.19/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/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4=
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
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/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -7,11 +7,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -103,17 +103,17 @@ func New(appOptions Options) *App {
|
||||
case "/wails/capabilities":
|
||||
err := assetserver.ServeFile(rw, path, globalApplication.capabilities.AsBytes())
|
||||
if err != nil {
|
||||
result.handleFatalError(fmt.Errorf("unable to serve capabilities: %s", err.Error()))
|
||||
result.fatal("unable to serve capabilities: %w", err)
|
||||
}
|
||||
case "/wails/flags":
|
||||
updatedOptions := result.impl.GetFlags(appOptions)
|
||||
flags, err := json.Marshal(updatedOptions)
|
||||
if err != nil {
|
||||
result.handleFatalError(fmt.Errorf("invalid flags provided to application: %s", err.Error()))
|
||||
result.fatal("invalid flags provided to application: %w", err)
|
||||
}
|
||||
err = assetserver.ServeFile(rw, path, flags)
|
||||
if err != nil {
|
||||
result.handleFatalError(fmt.Errorf("unable to serve flags: %s", err.Error()))
|
||||
result.fatal("unable to serve flags: %w", err)
|
||||
}
|
||||
default:
|
||||
next.ServeHTTP(rw, req)
|
||||
@ -130,40 +130,14 @@ func New(appOptions Options) *App {
|
||||
|
||||
srv, err := assetserver.NewAssetServer(opts)
|
||||
if err != nil {
|
||||
result.handleFatalError(fmt.Errorf("Fatal error in application initialisation: " + err.Error()))
|
||||
result.fatal("application initialisation failed: %w", err)
|
||||
}
|
||||
|
||||
result.assets = srv
|
||||
result.assets.LogDetails()
|
||||
|
||||
result.bindings, err = NewBindings(appOptions.Services, appOptions.BindAliases)
|
||||
if err != nil {
|
||||
result.handleFatalError(fmt.Errorf("Fatal error in application initialisation: " + err.Error()))
|
||||
}
|
||||
|
||||
for i, service := range appOptions.Services {
|
||||
if thisService, ok := service.instance.(ServiceStartup); ok {
|
||||
err := thisService.ServiceStartup(result.ctx, service.options)
|
||||
if err != nil {
|
||||
name := service.options.Name
|
||||
if name == "" {
|
||||
name = getServiceName(service.instance)
|
||||
}
|
||||
globalApplication.Logger.Error("ServiceStartup() failed shutting down application:", "service", name, "error", err.Error())
|
||||
// Run shutdown on all services that have already started
|
||||
for _, service := range appOptions.Services[:i] {
|
||||
if thisService, ok := service.instance.(ServiceShutdown); ok {
|
||||
err := thisService.ServiceShutdown()
|
||||
if err != nil {
|
||||
globalApplication.Logger.Error("Error shutting down service: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
// Shutdown the application
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
result.bindings = NewBindings(appOptions.MarshalError, appOptions.BindAliases)
|
||||
result.options.Services = slices.Clone(appOptions.Services)
|
||||
|
||||
// Process keybindings
|
||||
if result.options.KeyBindings != nil {
|
||||
@ -181,11 +155,11 @@ func New(appOptions Options) *App {
|
||||
if errors.Is(err, alreadyRunningError) && manager != nil {
|
||||
err = manager.notifyFirstInstance()
|
||||
if err != nil {
|
||||
globalApplication.error("Failed to notify first instance: " + err.Error())
|
||||
globalApplication.error("failed to notify first instance: %w", err)
|
||||
}
|
||||
os.Exit(appOptions.SingleInstance.ExitCode)
|
||||
}
|
||||
result.handleFatalError(fmt.Errorf("failed to initialize single instance manager: %w", err))
|
||||
result.fatal("failed to initialize single instance manager: %w", err)
|
||||
} else {
|
||||
result.singleInstanceManager = manager
|
||||
}
|
||||
@ -314,7 +288,8 @@ type App struct {
|
||||
menuItems map[uint]*MenuItem
|
||||
menuItemsLock sync.Mutex
|
||||
|
||||
// Running
|
||||
// Starting and running
|
||||
starting bool
|
||||
running bool
|
||||
runLock sync.Mutex
|
||||
pendingRun []runnable
|
||||
@ -350,7 +325,9 @@ type App struct {
|
||||
keyBindingsLock sync.RWMutex
|
||||
|
||||
// Shutdown
|
||||
performingShutdown bool
|
||||
performingShutdown bool
|
||||
shutdownLock sync.Mutex
|
||||
serviceShutdownLock sync.Mutex
|
||||
|
||||
// Shutdown tasks are run when the application is shutting down.
|
||||
// They are run in the order they are added and run on the main thread.
|
||||
@ -375,6 +352,7 @@ func (a *App) handleWarning(msg string) {
|
||||
a.Logger.Warn(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) handleError(err error) {
|
||||
if a.options.ErrorHandler != nil {
|
||||
a.options.ErrorHandler(err)
|
||||
@ -383,6 +361,25 @@ func (a *App) handleError(err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterService appends the given service to the list of bound services.
|
||||
// Registered services will be bound and initialised
|
||||
// in registration order upon calling [App.Run].
|
||||
//
|
||||
// RegisterService will log an error message
|
||||
// and discard the given service
|
||||
// if called after [App.Run].
|
||||
func (a *App) RegisterService(service Service) {
|
||||
a.runLock.Lock()
|
||||
defer a.runLock.Unlock()
|
||||
|
||||
if a.starting || a.running {
|
||||
a.error("services must be registered before running the application. Service '%s' will not be registered.", getServiceName(service))
|
||||
return
|
||||
}
|
||||
|
||||
a.options.Services = append(a.options.Services, service)
|
||||
}
|
||||
|
||||
// EmitEvent will emit an event
|
||||
func (a *App) EmitEvent(name string, data ...any) {
|
||||
a.customEventProcessor.Emit(&CustomEvent{
|
||||
@ -417,13 +414,7 @@ func (a *App) ResetEvents() {
|
||||
}
|
||||
|
||||
func (a *App) handleFatalError(err error) {
|
||||
var buffer strings.Builder
|
||||
buffer.WriteString("\n\n************************ FATAL ******************************\n")
|
||||
buffer.WriteString("* There has been a catastrophic failure in your application *\n")
|
||||
buffer.WriteString("********************* Error Details *************************\n")
|
||||
buffer.WriteString(err.Error())
|
||||
buffer.WriteString("*************************************************************\n")
|
||||
a.handleError(fmt.Errorf(buffer.String()))
|
||||
a.handleError(&FatalError{err: err})
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@ -587,6 +578,18 @@ func (a *App) NewSystemTray() *SystemTray {
|
||||
}
|
||||
|
||||
func (a *App) Run() error {
|
||||
a.runLock.Lock()
|
||||
// Prevent double invocations.
|
||||
if a.starting || a.running {
|
||||
a.runLock.Unlock()
|
||||
return errors.New("application is running or a previous run has failed")
|
||||
}
|
||||
// Block further service registrations.
|
||||
a.starting = true
|
||||
a.runLock.Unlock()
|
||||
|
||||
// Ensure application context is canceled in case of failures.
|
||||
defer a.cancel()
|
||||
|
||||
// Call post-create hooks
|
||||
err := a.preRun()
|
||||
@ -595,6 +598,24 @@ func (a *App) Run() error {
|
||||
}
|
||||
|
||||
a.impl = newPlatformApp(a)
|
||||
|
||||
// Ensure services are shut down in case of failures.
|
||||
defer a.shutdownServices()
|
||||
// Ensure application context is canceled before service shutdown (duplicate calls don't hurt).
|
||||
defer a.cancel()
|
||||
|
||||
// Startup services before dispatching any events.
|
||||
// No need to hold the lock here because a.options.Services may only change when a.running is false.
|
||||
services := a.options.Services
|
||||
a.options.Services = nil
|
||||
for i, service := range services {
|
||||
if err := a.startupService(service); err != nil {
|
||||
return fmt.Errorf("error starting service '%s': %w", getServiceName(service), err)
|
||||
}
|
||||
// Schedule started services for shutdown.
|
||||
a.options.Services = services[:i+1]
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
event := <-applicationEvents
|
||||
@ -641,17 +662,19 @@ func (a *App) Run() error {
|
||||
|
||||
a.runLock.Lock()
|
||||
a.running = true
|
||||
a.runLock.Unlock()
|
||||
|
||||
for _, systray := range a.pendingRun {
|
||||
// No need to hold the lock here because
|
||||
// - a.pendingRun may only change while a.running is false.
|
||||
// - runnables are scheduled asynchronously anyway.
|
||||
for _, pending := range a.pendingRun {
|
||||
go func() {
|
||||
defer handlePanic()
|
||||
systray.Run()
|
||||
pending.Run()
|
||||
}()
|
||||
}
|
||||
a.pendingRun = nil
|
||||
|
||||
a.runLock.Unlock()
|
||||
|
||||
// set the application menu
|
||||
if runtime.GOOS == "darwin" {
|
||||
a.impl.setApplicationMenu(a.ApplicationMenu)
|
||||
@ -660,27 +683,59 @@ func (a *App) Run() error {
|
||||
a.impl.setIcon(a.options.Icon)
|
||||
}
|
||||
|
||||
err = a.impl.run()
|
||||
return a.impl.run()
|
||||
}
|
||||
|
||||
func (a *App) startupService(service Service) error {
|
||||
err := a.bindings.Add(service)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("cannot bind service methods: %w", err)
|
||||
}
|
||||
|
||||
// Cancel the context
|
||||
a.cancel()
|
||||
|
||||
for _, service := range a.options.Services {
|
||||
// If it conforms to the ServiceShutdown interface, call the Shutdown method
|
||||
if thisService, ok := service.instance.(ServiceShutdown); ok {
|
||||
err := thisService.ServiceShutdown()
|
||||
if err != nil {
|
||||
a.error("Error shutting down service: " + err.Error())
|
||||
}
|
||||
if service.options.Route != "" {
|
||||
handler, ok := service.Instance().(http.Handler)
|
||||
if !ok {
|
||||
handler = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
http.Error(
|
||||
rw,
|
||||
fmt.Sprintf("Service '%s' does not handle HTTP requests", getServiceName(service)),
|
||||
http.StatusServiceUnavailable,
|
||||
)
|
||||
})
|
||||
}
|
||||
a.assets.AttachServiceHandler(service.options.Route, handler)
|
||||
}
|
||||
|
||||
if s, ok := service.instance.(ServiceStartup); ok {
|
||||
a.debug("Starting up service:", "name", getServiceName(service))
|
||||
return s.ServiceStartup(a.ctx, service.options)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) shutdownServices() {
|
||||
// Acquire lock to prevent double calls (defer in Run() + OnShutdown)
|
||||
a.serviceShutdownLock.Lock()
|
||||
defer a.serviceShutdownLock.Unlock()
|
||||
|
||||
// Ensure app context is canceled first (duplicate calls don't hurt).
|
||||
a.cancel()
|
||||
|
||||
for len(a.options.Services) > 0 {
|
||||
last := len(a.options.Services) - 1
|
||||
service := a.options.Services[last]
|
||||
a.options.Services = a.options.Services[:last] // Prevent double shutdowns
|
||||
|
||||
if s, ok := service.instance.(ServiceShutdown); ok {
|
||||
a.debug("Shutting down service:", "name", getServiceName(service))
|
||||
if err := s.ServiceShutdown(); err != nil {
|
||||
a.error("error shutting down service '%s': %w", getServiceName(service), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) handleApplicationEvent(event *ApplicationEvent) {
|
||||
defer handlePanic()
|
||||
a.applicationEventListenersLock.RLock()
|
||||
@ -721,7 +776,7 @@ func (a *App) handleDragAndDropMessage(event *dragAndDropMessage) {
|
||||
window, ok := a.windows[event.windowId]
|
||||
a.windowsLock.Unlock()
|
||||
if !ok {
|
||||
log.Printf("WebviewWindow #%d not found", event.windowId)
|
||||
a.warning("WebviewWindow #%d not found", event.windowId)
|
||||
return
|
||||
}
|
||||
// Get callback from window
|
||||
@ -735,7 +790,7 @@ func (a *App) handleWindowMessage(event *windowMessage) {
|
||||
window, ok := a.windows[event.windowId]
|
||||
a.windowsLock.RUnlock()
|
||||
if !ok {
|
||||
log.Printf("WebviewWindow #%d not found", event.windowId)
|
||||
a.warning("WebviewWindow #%d not found", event.windowId)
|
||||
return
|
||||
}
|
||||
// Check if the message starts with "wails:"
|
||||
@ -760,7 +815,7 @@ func (a *App) handleWindowEvent(event *windowEvent) {
|
||||
window, ok := a.windows[event.WindowID]
|
||||
a.windowsLock.RUnlock()
|
||||
if !ok {
|
||||
log.Printf("Window #%d not found", event.WindowID)
|
||||
a.warning("Window #%d not found", event.WindowID)
|
||||
return
|
||||
}
|
||||
window.HandleWindowEvent(event.EventID)
|
||||
@ -771,7 +826,7 @@ func (a *App) handleMenuItemClicked(menuItemID uint) {
|
||||
|
||||
menuItem := getMenuItemByID(menuItemID)
|
||||
if menuItem == nil {
|
||||
log.Printf("MenuItem #%d not found", menuItemID)
|
||||
a.warning("MenuItem #%d not found", menuItemID)
|
||||
return
|
||||
}
|
||||
menuItem.handleClick()
|
||||
@ -796,7 +851,17 @@ func (a *App) OnShutdown(f func()) {
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
a.shutdownTasks = append(a.shutdownTasks, f)
|
||||
|
||||
a.shutdownLock.Lock()
|
||||
|
||||
if !a.performingShutdown {
|
||||
defer a.shutdownLock.Unlock()
|
||||
a.shutdownTasks = append(a.shutdownTasks, f)
|
||||
return
|
||||
}
|
||||
|
||||
a.shutdownLock.Unlock()
|
||||
InvokeAsync(f)
|
||||
}
|
||||
|
||||
func (a *App) destroySystemTray(tray *SystemTray) {
|
||||
@ -808,14 +873,22 @@ func (a *App) destroySystemTray(tray *SystemTray) {
|
||||
}
|
||||
|
||||
func (a *App) cleanup() {
|
||||
a.shutdownLock.Lock()
|
||||
if a.performingShutdown {
|
||||
a.shutdownLock.Unlock()
|
||||
return
|
||||
}
|
||||
a.cancel() // Cancel app context before running shutdown hooks.
|
||||
a.performingShutdown = true
|
||||
a.shutdownLock.Unlock()
|
||||
|
||||
// No need to hold the lock here because a.shutdownTasks
|
||||
// may only change while a.performingShutdown is false.
|
||||
for _, shutdownTask := range a.shutdownTasks {
|
||||
InvokeSync(shutdownTask)
|
||||
}
|
||||
InvokeSync(func() {
|
||||
a.shutdownServices()
|
||||
a.windowsLock.RLock()
|
||||
for _, window := range a.windows {
|
||||
window.Close()
|
||||
@ -828,17 +901,23 @@ func (a *App) cleanup() {
|
||||
}
|
||||
a.systemTrays = nil
|
||||
a.systemTraysLock.Unlock()
|
||||
|
||||
// Cleanup single instance manager
|
||||
if a.singleInstanceManager != nil {
|
||||
a.singleInstanceManager.cleanup()
|
||||
}
|
||||
|
||||
a.postQuit()
|
||||
|
||||
if a.options.PostShutdown != nil {
|
||||
a.options.PostShutdown()
|
||||
}
|
||||
})
|
||||
// Cleanup single instance manager
|
||||
if a.singleInstanceManager != nil {
|
||||
a.singleInstanceManager.cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) Quit() {
|
||||
if a.impl != nil {
|
||||
InvokeSync(a.impl.destroy)
|
||||
a.postQuit()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1006,15 +1085,17 @@ func (a *App) GetWindowByName(name string) Window {
|
||||
|
||||
func (a *App) runOrDeferToAppRun(r runnable) {
|
||||
a.runLock.Lock()
|
||||
running := a.running
|
||||
if !running {
|
||||
a.pendingRun = append(a.pendingRun, r)
|
||||
}
|
||||
a.runLock.Unlock()
|
||||
|
||||
if running {
|
||||
r.Run()
|
||||
if !a.running {
|
||||
defer a.runLock.Unlock() // Defer unlocking for panic tolerance.
|
||||
a.pendingRun = append(a.pendingRun, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Unlock immediately to prevent deadlocks.
|
||||
// No TOC/TOU risk here because a.running can never switch back to false.
|
||||
a.runLock.Unlock()
|
||||
r.Run()
|
||||
}
|
||||
|
||||
func (a *App) processKeyBinding(acceleratorString string, window *WebviewWindow) bool {
|
||||
@ -1056,7 +1137,7 @@ func (a *App) handleWindowKeyEvent(event *windowKeyEvent) {
|
||||
window, ok := a.windows[event.windowId]
|
||||
a.windowsLock.RUnlock()
|
||||
if !ok {
|
||||
log.Printf("WebviewWindow #%d not found", event.windowId)
|
||||
a.warning("WebviewWindow #%d not found", event.windowId)
|
||||
return
|
||||
}
|
||||
// Get callback from window
|
||||
|
@ -362,7 +362,7 @@ func cleanup() {
|
||||
func (a *App) logPlatformInfo() {
|
||||
info, err := operatingsystem.Info()
|
||||
if err != nil {
|
||||
a.error("Error getting OS info: %s", err.Error())
|
||||
a.error("error getting OS info: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -3,9 +3,10 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/internal/assetserver"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v3/internal/assetserver"
|
||||
)
|
||||
|
||||
var devMode = false
|
||||
|
@ -208,7 +208,7 @@ func newPlatformApp(parent *App) *linuxApp {
|
||||
func (a *App) logPlatformInfo() {
|
||||
info, err := operatingsystem.Info()
|
||||
if err != nil {
|
||||
a.error("Error getting OS info: %s", err.Error())
|
||||
a.error("error getting OS info: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -31,11 +31,21 @@ type Options struct {
|
||||
// Services allows you to bind Go methods to the frontend.
|
||||
Services []Service
|
||||
|
||||
// MarshalError will be called if non-nil
|
||||
// to marshal to JSON the error values returned by service methods.
|
||||
//
|
||||
// MarshalError is not allowed to fail,
|
||||
// but it may return a nil slice to fall back
|
||||
// to the default error handling mechanism.
|
||||
//
|
||||
// If the returned slice is not nil, it must contain valid JSON.
|
||||
MarshalError func(error) []byte
|
||||
|
||||
// BindAliases allows you to specify alias IDs for your bound methods.
|
||||
// Example: `BindAliases: map[uint32]uint32{1: 1411160069}` states that alias ID 1 maps to the Go method with ID 1411160069.
|
||||
BindAliases map[uint32]uint32
|
||||
|
||||
// Logger i a slog.Logger instance used for logging Wails system messages (not application messages).
|
||||
// Logger is a slog.Logger instance used for logging Wails system messages (not application messages).
|
||||
// If not defined, a default logger is used.
|
||||
Logger *slog.Logger
|
||||
|
||||
@ -60,9 +70,17 @@ type Options struct {
|
||||
|
||||
// OnShutdown is called when the application is about to terminate.
|
||||
// This is useful for cleanup tasks.
|
||||
// The shutdown process blocks until this function returns
|
||||
// The shutdown process blocks until this function returns.
|
||||
OnShutdown func()
|
||||
|
||||
// PostShutdown is called after the application
|
||||
// has finished shutting down, just before process termination.
|
||||
// This is useful for testing and logging purposes
|
||||
// on platforms where the Run() method does not return.
|
||||
// When PostShutdown is called, the application instance is not usable anymore.
|
||||
// The shutdown process blocks until this function returns.
|
||||
PostShutdown func()
|
||||
|
||||
// ShouldQuit is a function that is called when the user tries to quit the application.
|
||||
// If the function returns true, the application will quit.
|
||||
// If the function returns false, the application will not quit.
|
||||
|
@ -3,7 +3,7 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
@ -215,7 +215,7 @@ func (m *windowsApp) wndProc(hwnd w32.HWND, msg uint32, wParam, lParam uintptr)
|
||||
if msg == w32.WM_DISPLAYCHANGE || (msg == w32.WM_SETTINGCHANGE && wParam == w32.SPI_SETWORKAREA) {
|
||||
err := m.processAndCacheScreens()
|
||||
if err != nil {
|
||||
m.parent.error(err.Error())
|
||||
m.parent.handleError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -318,14 +318,14 @@ func setupDPIAwareness() error {
|
||||
return w32.SetProcessDPIAware()
|
||||
}
|
||||
|
||||
return fmt.Errorf("no DPI awareness method supported")
|
||||
return errors.New("no DPI awareness method supported")
|
||||
}
|
||||
|
||||
func newPlatformApp(app *App) *windowsApp {
|
||||
|
||||
err := setupDPIAwareness()
|
||||
if err != nil {
|
||||
app.error(err.Error())
|
||||
app.handleError(err)
|
||||
}
|
||||
|
||||
result := &windowsApp{
|
||||
@ -337,7 +337,7 @@ func newPlatformApp(app *App) *windowsApp {
|
||||
|
||||
err = result.processAndCacheScreens()
|
||||
if err != nil {
|
||||
app.fatal(err.Error())
|
||||
app.handleFatalError(err)
|
||||
}
|
||||
|
||||
result.init()
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
@ -21,16 +20,22 @@ type CallOptions struct {
|
||||
Args []json.RawMessage `json:"args"`
|
||||
}
|
||||
|
||||
type PluginCallOptions struct {
|
||||
Name string `json:"name"`
|
||||
Args []json.RawMessage `json:"args"`
|
||||
type ErrorKind string
|
||||
|
||||
const (
|
||||
ReferenceError ErrorKind = "ReferenceError"
|
||||
TypeError ErrorKind = "TypeError"
|
||||
RuntimeError ErrorKind = "RuntimeError"
|
||||
)
|
||||
|
||||
type CallError struct {
|
||||
Kind ErrorKind `json:"kind"`
|
||||
Message string `json:"message"`
|
||||
Cause any `json:"cause,omitempty"`
|
||||
}
|
||||
|
||||
var reservedPluginMethods = []string{
|
||||
"Name",
|
||||
"Init",
|
||||
"Shutdown",
|
||||
"Exported",
|
||||
func (e *CallError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// Parameter defines a Go method parameter
|
||||
@ -61,66 +66,75 @@ func (p *Parameter) IsError() bool {
|
||||
// BoundMethod defines all the data related to a Go method that is
|
||||
// bound to the Wails application
|
||||
type BoundMethod struct {
|
||||
ID uint32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Inputs []*Parameter `json:"inputs,omitempty"`
|
||||
Outputs []*Parameter `json:"outputs,omitempty"`
|
||||
Comments string `json:"comments,omitempty"`
|
||||
Method reflect.Value `json:"-"`
|
||||
TypeName string
|
||||
PackagePath string
|
||||
ID uint32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Inputs []*Parameter `json:"inputs,omitempty"`
|
||||
Outputs []*Parameter `json:"outputs,omitempty"`
|
||||
Comments string `json:"comments,omitempty"`
|
||||
Method reflect.Value `json:"-"`
|
||||
FQN string
|
||||
|
||||
marshalError func(error) []byte
|
||||
needsContext bool
|
||||
}
|
||||
|
||||
type Bindings struct {
|
||||
marshalError func(error) []byte
|
||||
boundMethods map[string]*BoundMethod
|
||||
boundByID map[uint32]*BoundMethod
|
||||
methodAliases map[uint32]uint32
|
||||
}
|
||||
|
||||
func NewBindings(instances []Service, aliases map[uint32]uint32) (*Bindings, error) {
|
||||
app := Get()
|
||||
b := &Bindings{
|
||||
func NewBindings(marshalError func(error) []byte, aliases map[uint32]uint32) *Bindings {
|
||||
return &Bindings{
|
||||
marshalError: wrapErrorMarshaler(marshalError, defaultMarshalError),
|
||||
boundMethods: make(map[string]*BoundMethod),
|
||||
boundByID: make(map[uint32]*BoundMethod),
|
||||
methodAliases: aliases,
|
||||
}
|
||||
for _, binding := range instances {
|
||||
handler, ok := binding.Instance().(http.Handler)
|
||||
if ok && binding.options.Route != "" {
|
||||
app.assets.AttachServiceHandler(binding.options.Route, handler)
|
||||
}
|
||||
err := b.Add(binding.Instance())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Add the given named type pointer methods to the Bindings
|
||||
func (b *Bindings) Add(namedPtr interface{}) error {
|
||||
methods, err := b.getMethods(namedPtr)
|
||||
// Add adds the given service to the bindings.
|
||||
func (b *Bindings) Add(service Service) error {
|
||||
methods, err := getMethods(service.Instance())
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot bind value to app: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
marshalError := wrapErrorMarshaler(service.options.MarshalError, defaultMarshalError)
|
||||
|
||||
// Validate and log methods.
|
||||
for _, method := range methods {
|
||||
if _, ok := b.boundMethods[method.FQN]; ok {
|
||||
return fmt.Errorf("bound method '%s' is already registered. Please note that you can register at most one service of each type; additional instances must be wrapped in dedicated structs", method.FQN)
|
||||
}
|
||||
if boundMethod, ok := b.boundByID[method.ID]; ok {
|
||||
return fmt.Errorf("oh wow, we're sorry about this! Amazingly, a hash collision was detected for method '%s' (it generates the same hash as '%s'). To use this method, please rename it. Sorry :(", method.FQN, boundMethod.FQN)
|
||||
}
|
||||
|
||||
// Log
|
||||
attrs := []any{"fqn", method.FQN, "id", method.ID}
|
||||
if alias, ok := lo.FindKey(b.methodAliases, method.ID); ok {
|
||||
attrs = append(attrs, "alias", alias)
|
||||
}
|
||||
globalApplication.debug("Registering bound method:", attrs...)
|
||||
}
|
||||
|
||||
for _, method := range methods {
|
||||
// Add it as a regular method
|
||||
b.boundMethods[method.String()] = method
|
||||
// Store composite error marshaler
|
||||
method.marshalError = marshalError
|
||||
|
||||
// Register method
|
||||
b.boundMethods[method.FQN] = method
|
||||
b.boundByID[method.ID] = method
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the bound method with the given name
|
||||
func (b *Bindings) Get(options *CallOptions) *BoundMethod {
|
||||
method, ok := b.boundMethods[options.MethodName]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return method
|
||||
return b.boundMethods[options.MethodName]
|
||||
}
|
||||
|
||||
// GetByID returns the bound method with the given ID
|
||||
@ -131,29 +145,27 @@ func (b *Bindings) GetByID(id uint32) *BoundMethod {
|
||||
id = alias
|
||||
}
|
||||
}
|
||||
result := b.boundByID[id]
|
||||
return result
|
||||
|
||||
return b.boundByID[id]
|
||||
}
|
||||
|
||||
// GenerateID generates a unique ID for a binding
|
||||
func (b *Bindings) GenerateID(name string) (uint32, error) {
|
||||
id, err := hash.Fnv(name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Check if we already have it
|
||||
boundMethod, ok := b.boundByID[id]
|
||||
if ok {
|
||||
return 0, fmt.Errorf("oh wow, we're sorry about this! Amazingly, a hash collision was detected for method '%s' (it generates the same hash as '%s'). To continue, please rename it. Sorry :(", name, boundMethod.String())
|
||||
}
|
||||
return id, nil
|
||||
// internalServiceMethod is a set of methods
|
||||
// that are handled specially by the binding engine
|
||||
// and must not be exposed to the frontend.
|
||||
//
|
||||
// For simplicity we exclude these by name
|
||||
// without checking their signatures,
|
||||
// and so does the binding generator.
|
||||
var internalServiceMethods = map[string]bool{
|
||||
"ServiceName": true,
|
||||
"ServiceStartup": true,
|
||||
"ServiceShutdown": true,
|
||||
"ServeHTTP": true,
|
||||
}
|
||||
|
||||
func (b *BoundMethod) String() string {
|
||||
return fmt.Sprintf("%s.%s.%s", b.PackagePath, b.TypeName, b.Name)
|
||||
}
|
||||
var ctxType = reflect.TypeFor[context.Context]()
|
||||
|
||||
func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||
func getMethods(value any) ([]*BoundMethod, error) {
|
||||
// Create result placeholder
|
||||
var result []*BoundMethod
|
||||
|
||||
@ -180,42 +192,27 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||
return nil, fmt.Errorf("%s.%s is a generic type. Generic bound types are not supported", packagePath, namedType.String())
|
||||
}
|
||||
|
||||
ctxType := reflect.TypeFor[context.Context]()
|
||||
|
||||
// Process Methods
|
||||
for i := 0; i < ptrType.NumMethod(); i++ {
|
||||
methodDef := ptrType.Method(i)
|
||||
methodName := methodDef.Name
|
||||
method := namedValue.MethodByName(methodName)
|
||||
for i := range ptrType.NumMethod() {
|
||||
methodName := ptrType.Method(i).Name
|
||||
method := namedValue.Method(i)
|
||||
|
||||
if b.internalMethod(methodDef) {
|
||||
if internalServiceMethods[methodName] {
|
||||
continue
|
||||
}
|
||||
|
||||
fqn := fmt.Sprintf("%s.%s.%s", packagePath, typeName, methodName)
|
||||
|
||||
// Create new method
|
||||
boundMethod := &BoundMethod{
|
||||
Name: methodName,
|
||||
PackagePath: packagePath,
|
||||
TypeName: typeName,
|
||||
Inputs: nil,
|
||||
Outputs: nil,
|
||||
Comments: "",
|
||||
Method: method,
|
||||
ID: hash.Fnv(fqn),
|
||||
FQN: fqn,
|
||||
Name: methodName,
|
||||
Inputs: nil,
|
||||
Outputs: nil,
|
||||
Comments: "",
|
||||
Method: method,
|
||||
}
|
||||
var err error
|
||||
boundMethod.ID, err = b.GenerateID(boundMethod.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
args := []any{"name", boundMethod, "id", boundMethod.ID}
|
||||
if b.methodAliases != nil {
|
||||
alias, found := lo.FindKey(b.methodAliases, boundMethod.ID)
|
||||
if found {
|
||||
args = append(args, "alias", alias)
|
||||
}
|
||||
}
|
||||
globalApplication.debug("Adding method:", args...)
|
||||
|
||||
// Iterate inputs
|
||||
methodType := method.Type()
|
||||
@ -245,40 +242,23 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||
result = append(result, boundMethod)
|
||||
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (b *Bindings) internalMethod(def reflect.Method) bool {
|
||||
// Get the receiver type
|
||||
receiverType := def.Type.In(0)
|
||||
|
||||
// Create a new instance of the receiver type
|
||||
instance := reflect.New(receiverType.Elem()).Interface()
|
||||
|
||||
// Check if the instance implements any of our service interfaces
|
||||
// and if the method matches the interface method
|
||||
switch def.Name {
|
||||
case "ServiceName":
|
||||
if _, ok := instance.(ServiceName); ok {
|
||||
return true
|
||||
}
|
||||
case "ServiceStartup":
|
||||
if _, ok := instance.(ServiceStartup); ok {
|
||||
return true
|
||||
}
|
||||
case "ServiceShutdown":
|
||||
if _, ok := instance.(ServiceShutdown); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
func (b *BoundMethod) String() string {
|
||||
return b.FQN
|
||||
}
|
||||
|
||||
var errorType = reflect.TypeFor[error]()
|
||||
|
||||
// Call will attempt to call this bound method with the given args
|
||||
func (b *BoundMethod) Call(ctx context.Context, args []json.RawMessage) (returnValue interface{}, err error) {
|
||||
// Call will attempt to call this bound method with the given args.
|
||||
// If the call succeeds, result will be either a non-error return value (if there is only one)
|
||||
// or a slice of non-error return values (if there are more than one).
|
||||
//
|
||||
// If the arguments are mistyped or the call returns one or more non-nil error values,
|
||||
// result is nil and err is an instance of *[CallError].
|
||||
func (b *BoundMethod) Call(ctx context.Context, args []json.RawMessage) (result any, err error) {
|
||||
// Use a defer statement to capture panics
|
||||
defer handlePanic(handlePanicOptions{skipEnd: 5})
|
||||
argCount := len(args)
|
||||
@ -287,7 +267,10 @@ func (b *BoundMethod) Call(ctx context.Context, args []json.RawMessage) (returnV
|
||||
}
|
||||
|
||||
if argCount != len(b.Inputs) {
|
||||
err = fmt.Errorf("%s expects %d arguments, received %d", b.Name, len(b.Inputs), argCount)
|
||||
err = &CallError{
|
||||
Kind: TypeError,
|
||||
Message: fmt.Sprintf("%s expects %d arguments, got %d", b.FQN, len(b.Inputs), argCount),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -305,7 +288,11 @@ func (b *BoundMethod) Call(ctx context.Context, args []json.RawMessage) (returnV
|
||||
value := reflect.New(b.Inputs[base+index].ReflectType)
|
||||
err = json.Unmarshal(arg, value.Interface())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not parse argument #%d: %w", index, err)
|
||||
err = &CallError{
|
||||
Kind: TypeError,
|
||||
Message: fmt.Sprintf("could not parse argument #%d: %s", index, err),
|
||||
Cause: json.RawMessage(b.marshalError(err)),
|
||||
}
|
||||
return
|
||||
}
|
||||
callArgs[base+index] = value.Elem()
|
||||
@ -322,32 +309,73 @@ func (b *BoundMethod) Call(ctx context.Context, args []json.RawMessage) (returnV
|
||||
var nonErrorOutputs = make([]any, 0, len(callResults))
|
||||
var errorOutputs []error
|
||||
|
||||
for _, result := range callResults {
|
||||
if result.Type() == errorType {
|
||||
if result.IsNil() {
|
||||
for _, field := range callResults {
|
||||
if field.Type() == errorType {
|
||||
if field.IsNil() {
|
||||
continue
|
||||
}
|
||||
if errorOutputs == nil {
|
||||
errorOutputs = make([]error, 0, len(callResults)-len(nonErrorOutputs))
|
||||
nonErrorOutputs = nil
|
||||
}
|
||||
errorOutputs = append(errorOutputs, result.Interface().(error))
|
||||
errorOutputs = append(errorOutputs, field.Interface().(error))
|
||||
} else if nonErrorOutputs != nil {
|
||||
nonErrorOutputs = append(nonErrorOutputs, result.Interface())
|
||||
nonErrorOutputs = append(nonErrorOutputs, field.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
if errorOutputs != nil {
|
||||
err = errors.Join(errorOutputs...)
|
||||
if len(errorOutputs) > 0 {
|
||||
info := make([]json.RawMessage, len(errorOutputs))
|
||||
for i, err := range errorOutputs {
|
||||
info[i] = b.marshalError(err)
|
||||
}
|
||||
|
||||
cerr := &CallError{
|
||||
Kind: RuntimeError,
|
||||
Message: errors.Join(errorOutputs...).Error(),
|
||||
Cause: info,
|
||||
}
|
||||
if len(info) == 1 {
|
||||
cerr.Cause = info[0]
|
||||
}
|
||||
|
||||
err = cerr
|
||||
} else if len(nonErrorOutputs) == 1 {
|
||||
returnValue = nonErrorOutputs[0]
|
||||
result = nonErrorOutputs[0]
|
||||
} else if len(nonErrorOutputs) > 1 {
|
||||
returnValue = nonErrorOutputs
|
||||
result = nonErrorOutputs
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// wrapErrorMarshaler returns an error marshaling functions
|
||||
// that calls the primary marshaler first,
|
||||
// then falls back to the secondary one.
|
||||
func wrapErrorMarshaler(primary func(error) []byte, secondary func(error) []byte) func(error) []byte {
|
||||
if primary == nil {
|
||||
return secondary
|
||||
}
|
||||
|
||||
return func(err error) []byte {
|
||||
result := primary(err)
|
||||
if result == nil {
|
||||
result = secondary(err)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// defaultMarshalError implements the default error marshaling mechanism.
|
||||
func defaultMarshalError(err error) []byte {
|
||||
result, jsonErr := json.Marshal(&err)
|
||||
if jsonErr != nil {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// isPtr returns true if the value given is a pointer.
|
||||
func isPtr(value interface{}) bool {
|
||||
return reflect.ValueOf(value).Kind() == reflect.Ptr
|
||||
|
@ -44,7 +44,7 @@ func (t *TestService) Variadic(s ...string) []string {
|
||||
return s
|
||||
}
|
||||
|
||||
func (t *TestService) PositionalAndVariadic(a int, b ...string) int {
|
||||
func (t *TestService) PositionalAndVariadic(a int, _ ...string) int {
|
||||
return a
|
||||
}
|
||||
|
||||
@ -52,106 +52,103 @@ func (t *TestService) Slice(a []int) []int {
|
||||
return a
|
||||
}
|
||||
|
||||
func newArgs(jsonArgs ...string) []json.RawMessage {
|
||||
args := []json.RawMessage{}
|
||||
|
||||
func newArgs(jsonArgs ...string) (args []json.RawMessage) {
|
||||
for _, j := range jsonArgs {
|
||||
args = append(args, json.RawMessage(j))
|
||||
}
|
||||
return args
|
||||
return
|
||||
}
|
||||
|
||||
func TestBoundMethodCall(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
args []json.RawMessage
|
||||
err error
|
||||
err string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
method: "Nil",
|
||||
args: []json.RawMessage{},
|
||||
err: nil,
|
||||
err: "",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "string",
|
||||
method: "String",
|
||||
args: newArgs(`"foo"`),
|
||||
err: nil,
|
||||
err: "",
|
||||
expected: "foo",
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
method: "Multiple",
|
||||
args: newArgs(`"foo"`, "0", "false"),
|
||||
err: nil,
|
||||
err: "",
|
||||
expected: []interface{}{"foo", 0, false},
|
||||
},
|
||||
{
|
||||
name: "struct",
|
||||
method: "Struct",
|
||||
args: newArgs(`{ "name": "alice" }`),
|
||||
err: nil,
|
||||
err: "",
|
||||
expected: Person{Name: "alice"},
|
||||
},
|
||||
{
|
||||
name: "struct, nil error",
|
||||
method: "StructNil",
|
||||
args: newArgs(`{ "name": "alice" }`),
|
||||
err: nil,
|
||||
err: "",
|
||||
expected: Person{Name: "alice"},
|
||||
},
|
||||
{
|
||||
name: "struct, error",
|
||||
method: "StructError",
|
||||
args: newArgs(`{ "name": "alice" }`),
|
||||
err: errors.New("error"),
|
||||
err: "error",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid argument count",
|
||||
method: "Multiple",
|
||||
args: newArgs(`"foo"`),
|
||||
err: errors.New("expects 3 arguments, received 1"),
|
||||
err: "expects 3 arguments, got 1",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid argument type",
|
||||
method: "String",
|
||||
args: newArgs("1"),
|
||||
err: errors.New("could not parse"),
|
||||
err: "could not parse",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "variadic, no arguments",
|
||||
method: "Variadic",
|
||||
args: newArgs(`[]`), // variadic parameters are passed as arrays
|
||||
err: nil,
|
||||
err: "",
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "variadic",
|
||||
method: "Variadic",
|
||||
args: newArgs(`["foo", "bar"]`),
|
||||
err: nil,
|
||||
err: "",
|
||||
expected: []string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
name: "positional and variadic",
|
||||
method: "PositionalAndVariadic",
|
||||
args: newArgs("42", `[]`),
|
||||
err: nil,
|
||||
err: "",
|
||||
expected: 42,
|
||||
},
|
||||
{
|
||||
name: "slice",
|
||||
method: "Slice",
|
||||
args: newArgs(`[1,2,3]`),
|
||||
err: nil,
|
||||
err: "",
|
||||
expected: []int{1, 2, 3},
|
||||
},
|
||||
}
|
||||
@ -159,13 +156,11 @@ func TestBoundMethodCall(t *testing.T) {
|
||||
// init globalApplication
|
||||
_ = application.New(application.Options{})
|
||||
|
||||
bindings, err := application.NewBindings(
|
||||
[]application.Service{
|
||||
application.NewService(&TestService{}),
|
||||
}, make(map[uint32]uint32),
|
||||
)
|
||||
bindings := application.NewBindings(nil, nil)
|
||||
|
||||
err := bindings.Add(application.NewService(&TestService{}))
|
||||
if err != nil {
|
||||
t.Fatalf("application.NewBindings() error = %v\n", err)
|
||||
t.Fatalf("bindings.Add() error = %v\n", err)
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@ -180,13 +175,16 @@ func TestBoundMethodCall(t *testing.T) {
|
||||
}
|
||||
|
||||
result, err := method.Call(context.TODO(), tt.args)
|
||||
if tt.err != err && (tt.err == nil || err == nil || !strings.Contains(err.Error(), tt.err.Error())) {
|
||||
t.Fatalf("error: %v, expected error: %v", err, tt.err)
|
||||
if (tt.err == "") != (err == nil) || (err != nil && !strings.Contains(err.Error(), tt.err)) {
|
||||
expected := tt.err
|
||||
if expected == "" {
|
||||
expected = "nil"
|
||||
}
|
||||
t.Fatalf("error: %#v, expected error: %v", err, expected)
|
||||
}
|
||||
if !reflect.DeepEqual(result, tt.expected) {
|
||||
t.Fatalf("result: %v, expected result: %v", result, tt.expected)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ func (m *windowsDialog) show() {
|
||||
if m.dialog.window != nil {
|
||||
parentWindow, err = m.dialog.window.NativeWindowHandle()
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,12 +50,12 @@ func (m *windowsDialog) show() {
|
||||
// 3 is the application icon
|
||||
button, err = w32.MessageBoxWithIcon(parentWindow, message, title, 3, windows.MB_OK|windows.MB_USERICON)
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
} else {
|
||||
button, err = windows.MessageBox(windows.HWND(parentWindow), message, title, flags|windows.MB_SYSTEMMODAL)
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
}
|
||||
// This maps MessageBox return values to strings
|
||||
@ -114,7 +114,7 @@ func (m *windowOpenFileDialog) show() (chan string, error) {
|
||||
if m.dialog.window != nil {
|
||||
config.ParentWindowHandle, err = m.dialog.window.NativeWindowHandle()
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,7 +242,7 @@ func showCfdDialog(newDlg func() (cfd.Dialog, error), isMultiSelect bool) (any,
|
||||
defer func() {
|
||||
err := dlg.Release()
|
||||
if err != nil {
|
||||
globalApplication.error("Unable to release dialog: " + err.Error())
|
||||
globalApplication.error("unable to release dialog: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -3,14 +3,53 @@ package application
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Fatal(message string, args ...interface{}) {
|
||||
println("*********************** FATAL ***********************")
|
||||
println("There has been a catastrophic failure in your application.")
|
||||
println("Please report this error at https://github.com/wailsapp/wails/issues")
|
||||
println("******************** Error Details ******************")
|
||||
println(fmt.Sprintf(message, args...))
|
||||
println("*********************** FATAL ***********************")
|
||||
// FatalError instances are passed to the registered error handler
|
||||
// in case of catastrophic, unrecoverable failures that require immediate termination.
|
||||
// FatalError wraps the original error value in an informative message.
|
||||
// The underlying error may be retrieved through the [FatalError.Unwrap] method.
|
||||
type FatalError struct {
|
||||
err error
|
||||
internal bool
|
||||
}
|
||||
|
||||
// Internal returns true when the error was triggered from wails' internal code.
|
||||
func (e *FatalError) Internal() bool {
|
||||
return e.internal
|
||||
}
|
||||
|
||||
// Unwrap returns the original cause of the fatal error,
|
||||
// for easy inspection using the [errors.As] API.
|
||||
func (e *FatalError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
func (e *FatalError) Error() string {
|
||||
var buffer strings.Builder
|
||||
buffer.WriteString("\n\n******************************** FATAL *********************************\n")
|
||||
buffer.WriteString("* There has been a catastrophic failure in your application. *\n")
|
||||
if e.internal {
|
||||
buffer.WriteString("* Please report this error at https://github.com/wailsapp/wails/issues *\n")
|
||||
}
|
||||
buffer.WriteString("**************************** Error Details *****************************\n")
|
||||
buffer.WriteString(e.err.Error())
|
||||
buffer.WriteString("************************************************************************\n")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func Fatal(message string, args ...any) {
|
||||
err := &FatalError{
|
||||
err: fmt.Errorf(message, args...),
|
||||
internal: true,
|
||||
}
|
||||
|
||||
if globalApplication != nil {
|
||||
globalApplication.handleError(err)
|
||||
} else {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
168
v3/pkg/application/internal/tests/services/common.go
Normal file
168
v3/pkg/application/internal/tests/services/common.go
Normal file
@ -0,0 +1,168 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Id int
|
||||
T *testing.T
|
||||
Seq *atomic.Int64
|
||||
Options application.ServiceOptions
|
||||
StartupErr bool
|
||||
ShutdownErr bool
|
||||
}
|
||||
|
||||
func Configure[T any, P interface {
|
||||
*T
|
||||
Configure(Config)
|
||||
}](srv P, c Config) application.Service {
|
||||
srv.Configure(c)
|
||||
return application.NewServiceWithOptions(srv, c.Options)
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
Id int
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("service #%d mock failure", e.Id)
|
||||
}
|
||||
|
||||
type Startupper struct {
|
||||
Config
|
||||
startup int64
|
||||
}
|
||||
|
||||
func (s *Startupper) Configure(c Config) {
|
||||
s.Config = c
|
||||
}
|
||||
|
||||
func (s *Startupper) Id() int {
|
||||
return s.Config.Id
|
||||
}
|
||||
|
||||
func (s *Startupper) StartupSeq() int64 {
|
||||
return s.startup
|
||||
}
|
||||
|
||||
func (s *Startupper) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||
if s.startup != 0 {
|
||||
s.T.Errorf("Double startup for service #%d: first at seq=%d, then at seq=%d", s.Id(), s.startup, s.Seq.Load())
|
||||
return nil
|
||||
}
|
||||
|
||||
s.startup = s.Seq.Add(1)
|
||||
|
||||
if diff := cmp.Diff(s.Options, options); diff != "" {
|
||||
s.T.Errorf("Options mismatch for service #%d (-want +got):\n%s", s.Id(), diff)
|
||||
}
|
||||
|
||||
if s.StartupErr {
|
||||
return &Error{Id: s.Id()}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type Shutdowner struct {
|
||||
Config
|
||||
shutdown int64
|
||||
}
|
||||
|
||||
func (s *Shutdowner) Configure(c Config) {
|
||||
s.Config = c
|
||||
}
|
||||
|
||||
func (s *Shutdowner) Id() int {
|
||||
return s.Config.Id
|
||||
}
|
||||
|
||||
func (s *Shutdowner) ShutdownSeq() int64 {
|
||||
return s.shutdown
|
||||
}
|
||||
|
||||
func (s *Shutdowner) ServiceShutdown() error {
|
||||
if s.shutdown != 0 {
|
||||
s.T.Errorf("Double shutdown for service #%d: first at seq=%d, then at seq=%d", s.Id(), s.shutdown, s.Seq.Load())
|
||||
return nil
|
||||
}
|
||||
|
||||
s.shutdown = s.Seq.Add(1)
|
||||
|
||||
if s.ShutdownErr {
|
||||
return &Error{Id: s.Id()}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type StartupShutdowner struct {
|
||||
Config
|
||||
startup int64
|
||||
shutdown int64
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (s *StartupShutdowner) Configure(c Config) {
|
||||
s.Config = c
|
||||
}
|
||||
|
||||
func (s *StartupShutdowner) Id() int {
|
||||
return s.Config.Id
|
||||
}
|
||||
|
||||
func (s *StartupShutdowner) StartupSeq() int64 {
|
||||
return s.startup
|
||||
}
|
||||
|
||||
func (s *StartupShutdowner) ShutdownSeq() int64 {
|
||||
return s.shutdown
|
||||
}
|
||||
|
||||
func (s *StartupShutdowner) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||
if s.startup != 0 {
|
||||
s.T.Errorf("Double startup for service #%d: first at seq=%d, then at seq=%d", s.Id(), s.startup, s.Seq.Load())
|
||||
return nil
|
||||
}
|
||||
|
||||
s.startup = s.Seq.Add(1)
|
||||
s.ctx = ctx
|
||||
|
||||
if diff := cmp.Diff(s.Options, options); diff != "" {
|
||||
s.T.Errorf("Options mismatch for service #%d (-want +got):\n%s", s.Id(), diff)
|
||||
}
|
||||
|
||||
if s.StartupErr {
|
||||
return &Error{Id: s.Id()}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StartupShutdowner) ServiceShutdown() error {
|
||||
if s.shutdown != 0 {
|
||||
s.T.Errorf("Double shutdown for service #%d: first at seq=%d, then at seq=%d", s.Id(), s.shutdown, s.Seq.Load())
|
||||
return nil
|
||||
}
|
||||
|
||||
s.shutdown = s.Seq.Add(1)
|
||||
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
default:
|
||||
s.T.Errorf("Service #%d shut down before context cancellation", s.Id())
|
||||
}
|
||||
|
||||
if s.ShutdownErr {
|
||||
return &Error{Id: s.Id()}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package shutdown
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests"
|
||||
svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
apptest.Main(m)
|
||||
}
|
||||
|
||||
type (
|
||||
Service1 struct{ svctest.Shutdowner }
|
||||
Service2 struct{ svctest.Shutdowner }
|
||||
Service3 struct{ svctest.Shutdowner }
|
||||
Service4 struct{ svctest.Shutdowner }
|
||||
Service5 struct{ svctest.Shutdowner }
|
||||
Service6 struct{ svctest.Shutdowner }
|
||||
)
|
||||
|
||||
func TestServiceShutdown(t *testing.T) {
|
||||
var seq atomic.Int64
|
||||
|
||||
services := []application.Service{
|
||||
svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service5{}, svctest.Config{Id: 4, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq}),
|
||||
}
|
||||
|
||||
app := apptest.New(t, application.Options{
|
||||
Services: services[:3],
|
||||
})
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
app.RegisterService(services[3])
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
app.RegisterService(services[4])
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
app.RegisterService(services[5])
|
||||
|
||||
app.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) {
|
||||
app.Quit()
|
||||
})
|
||||
|
||||
err := apptest.Run(t, app)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if count := seq.Load(); count != int64(len(services)) {
|
||||
t.Errorf("Wrong shutdown call count: wanted %d, got %d", len(services), count)
|
||||
}
|
||||
|
||||
validate(t, services[0], 5)
|
||||
validate(t, services[1], 4)
|
||||
validate(t, services[2], 2, 3)
|
||||
validate(t, services[3], 1)
|
||||
validate(t, services[4], 1)
|
||||
validate(t, services[5], 0)
|
||||
}
|
||||
|
||||
func validate(t *testing.T, svc application.Service, prev ...int64) {
|
||||
id := svc.Instance().(interface{ Id() int }).Id()
|
||||
seq := svc.Instance().(interface{ ShutdownSeq() int64 }).ShutdownSeq()
|
||||
|
||||
if seq == 0 {
|
||||
t.Errorf("Service #%d did not shut down", id)
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range prev {
|
||||
if seq <= p {
|
||||
t.Errorf("Wrong shutdown sequence number for service #%d: wanted >%d, got %d", id, p, seq)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
package shutdownerror
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests"
|
||||
svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
apptest.Main(m)
|
||||
}
|
||||
|
||||
type (
|
||||
Service1 struct{ svctest.Shutdowner }
|
||||
Service2 struct{ svctest.Shutdowner }
|
||||
Service3 struct{ svctest.Shutdowner }
|
||||
Service4 struct{ svctest.Shutdowner }
|
||||
Service5 struct{ svctest.Shutdowner }
|
||||
Service6 struct{ svctest.Shutdowner }
|
||||
)
|
||||
|
||||
func TestServiceShutdownError(t *testing.T) {
|
||||
var seq atomic.Int64
|
||||
|
||||
services := []application.Service{
|
||||
svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq, ShutdownErr: true}),
|
||||
svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service5{}, svctest.Config{Id: 4, T: t, Seq: &seq, ShutdownErr: true}),
|
||||
svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq, ShutdownErr: true}),
|
||||
}
|
||||
|
||||
expectedShutdownErrors := []int{5, 4, 1}
|
||||
var errCount atomic.Int64
|
||||
|
||||
var app *application.App
|
||||
app = apptest.New(t, application.Options{
|
||||
Services: services[:3],
|
||||
ErrorHandler: func(err error) {
|
||||
var mock *svctest.Error
|
||||
if !errors.As(err, &mock) {
|
||||
app.Logger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
i := int(errCount.Add(1) - 1)
|
||||
if i < len(expectedShutdownErrors) && mock.Id == expectedShutdownErrors[i] {
|
||||
return
|
||||
}
|
||||
|
||||
cut := min(i, len(expectedShutdownErrors))
|
||||
if slices.Contains(expectedShutdownErrors[:cut], mock.Id) {
|
||||
t.Errorf("Late or duplicate shutdown error for service #%d", mock.Id)
|
||||
} else if slices.Contains(expectedShutdownErrors[cut:], mock.Id) {
|
||||
t.Errorf("Early shutdown error for service #%d", mock.Id)
|
||||
} else {
|
||||
t.Errorf("Unexpected shutdown error for service #%d", mock.Id)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
app.RegisterService(services[3])
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
app.RegisterService(services[4])
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
app.RegisterService(services[5])
|
||||
|
||||
app.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) {
|
||||
app.Quit()
|
||||
})
|
||||
|
||||
err := apptest.Run(t, app)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ec := errCount.Load(); ec != int64(len(expectedShutdownErrors)) {
|
||||
t.Errorf("Wrong shutdown error count: wanted %d, got %d", len(expectedShutdownErrors), ec)
|
||||
}
|
||||
|
||||
if count := seq.Load(); count != int64(len(services)) {
|
||||
t.Errorf("Wrong shutdown call count: wanted %d, got %d", len(services), count)
|
||||
}
|
||||
|
||||
validate(t, services[0], 5)
|
||||
validate(t, services[1], 4)
|
||||
validate(t, services[2], 2, 3)
|
||||
validate(t, services[3], 1)
|
||||
validate(t, services[4], 1)
|
||||
validate(t, services[5], 0)
|
||||
}
|
||||
|
||||
func validate(t *testing.T, svc application.Service, prev ...int64) {
|
||||
id := svc.Instance().(interface{ Id() int }).Id()
|
||||
seq := svc.Instance().(interface{ ShutdownSeq() int64 }).ShutdownSeq()
|
||||
|
||||
if seq == 0 {
|
||||
t.Errorf("Service #%d did not shut down", id)
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range prev {
|
||||
if seq <= p {
|
||||
t.Errorf("Wrong shutdown sequence number for service #%d: wanted >%d, got %d", id, p, seq)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package startup
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests"
|
||||
svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
apptest.Main(m)
|
||||
}
|
||||
|
||||
type (
|
||||
Service1 struct{ svctest.Startupper }
|
||||
Service2 struct{ svctest.Startupper }
|
||||
Service3 struct{ svctest.Startupper }
|
||||
Service4 struct{ svctest.Startupper }
|
||||
Service5 struct{ svctest.Startupper }
|
||||
Service6 struct{ svctest.Startupper }
|
||||
)
|
||||
|
||||
func TestServiceStartup(t *testing.T) {
|
||||
var seq atomic.Int64
|
||||
|
||||
services := []application.Service{
|
||||
svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq, Options: application.ServiceOptions{
|
||||
Name: "I am service 2",
|
||||
}}),
|
||||
svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq, Options: application.ServiceOptions{
|
||||
Route: "/mounted/here",
|
||||
}}),
|
||||
svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service5{}, svctest.Config{Id: 4, Seq: &seq, Options: application.ServiceOptions{
|
||||
Name: "I am service 5",
|
||||
Route: "/mounted/there",
|
||||
}}),
|
||||
svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq, Options: application.ServiceOptions{
|
||||
Name: "I am service 6",
|
||||
Route: "/mounted/elsewhere",
|
||||
}}),
|
||||
}
|
||||
|
||||
app := apptest.New(t, application.Options{
|
||||
Services: services[:3],
|
||||
})
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
app.RegisterService(services[3])
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
app.RegisterService(services[4])
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
app.RegisterService(services[5])
|
||||
|
||||
app.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) {
|
||||
app.Quit()
|
||||
})
|
||||
|
||||
err := apptest.Run(t, app)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if count := seq.Load(); count != int64(len(services)) {
|
||||
t.Errorf("Wrong startup call count: wanted %d, got %d", len(services), count)
|
||||
}
|
||||
|
||||
validate(t, services[0], 0)
|
||||
validate(t, services[1], 1)
|
||||
validate(t, services[2], 2)
|
||||
validate(t, services[3], 3)
|
||||
validate(t, services[4], 3)
|
||||
validate(t, services[5], 4, 5)
|
||||
}
|
||||
|
||||
func validate(t *testing.T, svc application.Service, prev ...int64) {
|
||||
id := svc.Instance().(interface{ Id() int }).Id()
|
||||
seq := svc.Instance().(interface{ StartupSeq() int64 }).StartupSeq()
|
||||
|
||||
if seq == 0 {
|
||||
t.Errorf("Service #%d did not start up", id)
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range prev {
|
||||
if seq <= p {
|
||||
t.Errorf("Wrong startup sequence number for service #%d: wanted >%d, got %d", id, p, seq)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package startuperror
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests"
|
||||
svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
apptest.Main(m)
|
||||
}
|
||||
|
||||
type (
|
||||
Service1 struct{ svctest.Startupper }
|
||||
Service2 struct{ svctest.Startupper }
|
||||
Service3 struct{ svctest.Startupper }
|
||||
Service4 struct{ svctest.Startupper }
|
||||
Service5 struct{ svctest.Startupper }
|
||||
Service6 struct{ svctest.Startupper }
|
||||
)
|
||||
|
||||
func TestServiceStartupError(t *testing.T) {
|
||||
var seq atomic.Int64
|
||||
|
||||
services := []application.Service{
|
||||
svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq, StartupErr: true}),
|
||||
svctest.Configure(&Service5{}, svctest.Config{Id: 4, T: t, Seq: &seq, StartupErr: true}),
|
||||
svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq, StartupErr: true}),
|
||||
}
|
||||
|
||||
app := apptest.New(t, application.Options{
|
||||
Services: services[:3],
|
||||
})
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
app.RegisterService(services[3])
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
app.RegisterService(services[4])
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
app.RegisterService(services[5])
|
||||
|
||||
app.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) {
|
||||
t.Errorf("Application started")
|
||||
app.Quit()
|
||||
})
|
||||
|
||||
var mock *svctest.Error
|
||||
|
||||
err := apptest.Run(t, app)
|
||||
if err != nil {
|
||||
if !errors.As(err, &mock) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if mock == nil {
|
||||
t.Fatal("Wanted startup error for service #3 or #4, got none")
|
||||
} else if mock.Id != 3 && mock.Id != 4 {
|
||||
t.Errorf("Wanted startup error for service #3 or #4, got #%d", mock.Id)
|
||||
}
|
||||
|
||||
if count := seq.Load(); count != 4 {
|
||||
t.Errorf("Wrong startup call count: wanted %d, got %d", 4, count)
|
||||
}
|
||||
|
||||
validate(t, services[0], 0)
|
||||
validate(t, services[1], 1)
|
||||
validate(t, services[2], 2)
|
||||
validate(t, services[mock.Id], 3)
|
||||
|
||||
notStarted := 3
|
||||
if mock.Id == 3 {
|
||||
notStarted = 4
|
||||
}
|
||||
|
||||
if seq := services[notStarted].Instance().(interface{ StartupSeq() int64 }).StartupSeq(); seq != 0 {
|
||||
t.Errorf("Service #%d started up unexpectedly at seq=%d", notStarted, seq)
|
||||
}
|
||||
if seq := services[5].Instance().(interface{ StartupSeq() int64 }).StartupSeq(); seq != 0 {
|
||||
t.Errorf("Service #5 started up unexpectedly at seq=%d", seq)
|
||||
}
|
||||
}
|
||||
|
||||
func validate(t *testing.T, svc application.Service, prev ...int64) {
|
||||
id := svc.Instance().(interface{ Id() int }).Id()
|
||||
seq := svc.Instance().(interface{ StartupSeq() int64 }).StartupSeq()
|
||||
|
||||
if seq == 0 {
|
||||
t.Errorf("Service #%d did not start up", id)
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range prev {
|
||||
if seq <= p {
|
||||
t.Errorf("Wrong startup sequence number for service #%d: wanted >%d, got %d", id, p, seq)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package startupshutdown
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests"
|
||||
svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
apptest.Main(m)
|
||||
}
|
||||
|
||||
type (
|
||||
Service1 struct{ svctest.StartupShutdowner }
|
||||
Service2 struct{ svctest.StartupShutdowner }
|
||||
Service3 struct{ svctest.StartupShutdowner }
|
||||
Service4 struct{ svctest.StartupShutdowner }
|
||||
Service5 struct{ svctest.StartupShutdowner }
|
||||
Service6 struct{ svctest.StartupShutdowner }
|
||||
)
|
||||
|
||||
func TestServiceStartupShutdown(t *testing.T) {
|
||||
var seq atomic.Int64
|
||||
|
||||
services := []application.Service{
|
||||
svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service5{}, svctest.Config{Id: 4, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq}),
|
||||
}
|
||||
|
||||
app := apptest.New(t, application.Options{
|
||||
Services: services[:3],
|
||||
})
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
app.RegisterService(services[3])
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
app.RegisterService(services[4])
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
app.RegisterService(services[5])
|
||||
|
||||
app.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) {
|
||||
if count := seq.Load(); count != int64(len(services)) {
|
||||
t.Errorf("Wrong startup call count: wanted %d, got %d", len(services), count)
|
||||
}
|
||||
seq.Store(0)
|
||||
app.Quit()
|
||||
})
|
||||
|
||||
err := apptest.Run(t, app)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if count := seq.Load(); count != int64(len(services)) {
|
||||
t.Errorf("Wrong shutdown call count: wanted %d, got %d", len(services), count)
|
||||
}
|
||||
|
||||
bound := int64(len(services)) + 1
|
||||
validate(t, services[0], bound)
|
||||
validate(t, services[1], bound)
|
||||
validate(t, services[2], bound)
|
||||
validate(t, services[3], bound)
|
||||
validate(t, services[4], bound)
|
||||
validate(t, services[5], bound)
|
||||
}
|
||||
|
||||
func validate(t *testing.T, svc application.Service, bound int64) {
|
||||
id := svc.Instance().(interface{ Id() int }).Id()
|
||||
startup := svc.Instance().(interface{ StartupSeq() int64 }).StartupSeq()
|
||||
shutdown := svc.Instance().(interface{ ShutdownSeq() int64 }).ShutdownSeq()
|
||||
|
||||
if startup == 0 && shutdown == 0 {
|
||||
t.Errorf("Service #%d did not start nor shut down", id)
|
||||
return
|
||||
} else if startup == 0 {
|
||||
t.Errorf("Service #%d started, but did not shut down", id)
|
||||
return
|
||||
} else if shutdown == 0 {
|
||||
t.Errorf("Service #%d shut down, but did not start", id)
|
||||
return
|
||||
}
|
||||
|
||||
if shutdown != bound-startup {
|
||||
t.Errorf("Wrong sequence numbers for service #%d: wanted either %d..%d or %d..%d, got %d..%d", id, startup, bound-startup, bound-shutdown, shutdown, startup, shutdown)
|
||||
}
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
package startupshutdownerror
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
apptest "github.com/wailsapp/wails/v3/pkg/application/internal/tests"
|
||||
svctest "github.com/wailsapp/wails/v3/pkg/application/internal/tests/services"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
apptest.Main(m)
|
||||
}
|
||||
|
||||
type (
|
||||
Service1 struct{ svctest.StartupShutdowner }
|
||||
Service2 struct{ svctest.StartupShutdowner }
|
||||
Service3 struct{ svctest.StartupShutdowner }
|
||||
Service4 struct{ svctest.StartupShutdowner }
|
||||
Service5 struct{ svctest.StartupShutdowner }
|
||||
Service6 struct{ svctest.StartupShutdowner }
|
||||
)
|
||||
|
||||
func TestServiceStartupShutdownError(t *testing.T) {
|
||||
var seq atomic.Int64
|
||||
|
||||
services := []application.Service{
|
||||
svctest.Configure(&Service1{}, svctest.Config{Id: 0, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service2{}, svctest.Config{Id: 1, T: t, Seq: &seq, ShutdownErr: true}),
|
||||
svctest.Configure(&Service3{}, svctest.Config{Id: 2, T: t, Seq: &seq}),
|
||||
svctest.Configure(&Service4{}, svctest.Config{Id: 3, T: t, Seq: &seq, StartupErr: true, ShutdownErr: true}),
|
||||
svctest.Configure(&Service5{}, svctest.Config{Id: 4, T: t, Seq: &seq, StartupErr: true, ShutdownErr: true}),
|
||||
svctest.Configure(&Service6{}, svctest.Config{Id: 5, T: t, Seq: &seq, StartupErr: true, ShutdownErr: true}),
|
||||
}
|
||||
|
||||
expectedShutdownErrors := []int{1}
|
||||
var errCount atomic.Int64
|
||||
|
||||
var app *application.App
|
||||
app = apptest.New(t, application.Options{
|
||||
Services: services[:3],
|
||||
ErrorHandler: func(err error) {
|
||||
var mock *svctest.Error
|
||||
if !errors.As(err, &mock) {
|
||||
app.Logger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
i := int(errCount.Add(1) - 1)
|
||||
if i < len(expectedShutdownErrors) && mock.Id == expectedShutdownErrors[i] {
|
||||
return
|
||||
}
|
||||
|
||||
cut := min(i, len(expectedShutdownErrors))
|
||||
if slices.Contains(expectedShutdownErrors[:cut], mock.Id) {
|
||||
t.Errorf("Late or duplicate shutdown error for service #%d", mock.Id)
|
||||
} else if slices.Contains(expectedShutdownErrors[cut:], mock.Id) {
|
||||
t.Errorf("Early shutdown error for service #%d", mock.Id)
|
||||
} else {
|
||||
t.Errorf("Unexpected shutdown error for service #%d", mock.Id)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
app.RegisterService(services[3])
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
app.RegisterService(services[4])
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
app.RegisterService(services[5])
|
||||
|
||||
app.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) {
|
||||
t.Errorf("Application started")
|
||||
app.Quit()
|
||||
})
|
||||
|
||||
var mock *svctest.Error
|
||||
|
||||
err := apptest.Run(t, app)
|
||||
if err != nil {
|
||||
if !errors.As(err, &mock) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if mock == nil {
|
||||
t.Fatal("Wanted error for service #3 or #4, got none")
|
||||
} else if mock.Id != 3 && mock.Id != 4 {
|
||||
t.Errorf("Wanted error for service #3 or #4, got #%d", mock.Id)
|
||||
}
|
||||
|
||||
if ec := errCount.Load(); ec != int64(len(expectedShutdownErrors)) {
|
||||
t.Errorf("Wrong shutdown error count: wanted %d, got %d", len(expectedShutdownErrors), ec)
|
||||
}
|
||||
|
||||
if count := seq.Load(); count != 4+3 {
|
||||
t.Errorf("Wrong startup+shutdown call count: wanted %d+%d, got %d", 4, 3, count)
|
||||
}
|
||||
|
||||
validate(t, services[0], true, true)
|
||||
validate(t, services[1], true, true)
|
||||
validate(t, services[2], true, true)
|
||||
validate(t, services[3], mock.Id == 3, false)
|
||||
validate(t, services[4], mock.Id == 4, false)
|
||||
validate(t, services[5], false, false)
|
||||
}
|
||||
|
||||
func validate(t *testing.T, svc application.Service, startup bool, shutdown bool) {
|
||||
id := svc.Instance().(interface{ Id() int }).Id()
|
||||
startupSeq := svc.Instance().(interface{ StartupSeq() int64 }).StartupSeq()
|
||||
shutdownSeq := svc.Instance().(interface{ ShutdownSeq() int64 }).ShutdownSeq()
|
||||
|
||||
if startup != (startupSeq != 0) {
|
||||
if startupSeq == 0 {
|
||||
t.Errorf("Service #%d did not start up", id)
|
||||
} else {
|
||||
t.Errorf("Unexpected startup for service #%d at seq=%d", id, startupSeq)
|
||||
}
|
||||
}
|
||||
|
||||
if shutdown != (shutdownSeq != 0) {
|
||||
if shutdownSeq == 0 {
|
||||
t.Errorf("Service #%d did not shut down", id)
|
||||
} else {
|
||||
t.Errorf("Unexpected shutdown for service #%d at seq=%d", id, shutdownSeq)
|
||||
}
|
||||
}
|
||||
}
|
74
v3/pkg/application/internal/tests/utils.go
Normal file
74
v3/pkg/application/internal/tests/utils.go
Normal file
@ -0,0 +1,74 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
var appChan chan *application.App = make(chan *application.App, 1)
|
||||
var errChan chan error = make(chan error, 1)
|
||||
var endChan chan error = make(chan error, 1)
|
||||
|
||||
func init() { runtime.LockOSThread() }
|
||||
|
||||
func New(t *testing.T, options application.Options) *application.App {
|
||||
var app *application.App
|
||||
|
||||
app = application.Get()
|
||||
if app != nil {
|
||||
return app
|
||||
}
|
||||
|
||||
if options.Name == "" {
|
||||
options.Name = t.Name()
|
||||
}
|
||||
|
||||
errorHandler := options.ErrorHandler
|
||||
options.ErrorHandler = func(err error) {
|
||||
if fatal := (*application.FatalError)(nil); errors.As(err, &fatal) {
|
||||
endChan <- err
|
||||
select {} // Block forever
|
||||
} else if errorHandler != nil {
|
||||
errorHandler(err)
|
||||
} else {
|
||||
app.Logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
postShutdown := options.PostShutdown
|
||||
options.PostShutdown = func() {
|
||||
if postShutdown != nil {
|
||||
postShutdown()
|
||||
}
|
||||
endChan <- nil
|
||||
select {} // Block forever
|
||||
}
|
||||
|
||||
return application.New(options)
|
||||
}
|
||||
|
||||
func Run(t *testing.T, app *application.App) error {
|
||||
appChan <- app
|
||||
select {
|
||||
case err := <-errChan:
|
||||
return err
|
||||
case fatal := <-endChan:
|
||||
if fatal != nil {
|
||||
t.Fatal(fatal)
|
||||
}
|
||||
return fatal
|
||||
}
|
||||
}
|
||||
|
||||
func Main(m *testing.M) {
|
||||
go func() {
|
||||
os.Exit(m.Run())
|
||||
}()
|
||||
|
||||
errChan <- (<-appChan).Run()
|
||||
select {} // Block forever
|
||||
}
|
@ -465,7 +465,7 @@ func (a *linuxApp) setIcon(icon []byte) {
|
||||
var gerror *C.GError
|
||||
pixbuf := C.gdk_pixbuf_new_from_stream(stream, nil, &gerror)
|
||||
if gerror != nil {
|
||||
a.parent.error("Failed to load application icon: " + C.GoString(gerror.message))
|
||||
a.parent.error("failed to load application icon: %s", C.GoString(gerror.message))
|
||||
C.g_error_free(gerror)
|
||||
return
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
@ -226,15 +224,13 @@ func NewRole(role Role) *MenuItem {
|
||||
result = NewHelpMenuItem()
|
||||
|
||||
default:
|
||||
globalApplication.error(fmt.Sprintf("No support for role: %v", role))
|
||||
os.Exit(1)
|
||||
globalApplication.error("no support for role: %v", role)
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
return nil
|
||||
if result != nil {
|
||||
result.role = role
|
||||
}
|
||||
|
||||
result.role = role
|
||||
return result
|
||||
}
|
||||
|
||||
@ -279,7 +275,7 @@ func (m *MenuItem) handleClick() {
|
||||
func (m *MenuItem) SetAccelerator(shortcut string) *MenuItem {
|
||||
accelerator, err := parseAccelerator(shortcut)
|
||||
if err != nil {
|
||||
globalApplication.error("invalid accelerator. %v", err.Error())
|
||||
globalApplication.error("invalid accelerator: %w", err)
|
||||
return m
|
||||
}
|
||||
m.accelerator = accelerator
|
||||
|
@ -3,8 +3,9 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/pkg/w32"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/w32"
|
||||
)
|
||||
|
||||
type windowsMenuItem struct {
|
||||
@ -121,7 +122,7 @@ func (m *windowsMenuItem) setBitmap(bitmap []byte) {
|
||||
// Set the icon
|
||||
err := w32.SetMenuIcons(m.hMenu, m.id, bitmap, nil)
|
||||
if err != nil {
|
||||
globalApplication.error("Unable to set bitmap on menu item: %s", err.Error())
|
||||
globalApplication.error("unable to set bitmap on menu item: %w", err)
|
||||
return
|
||||
}
|
||||
m.update()
|
||||
|
@ -3,6 +3,7 @@ package application
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
@ -41,12 +42,12 @@ func NewMessageProcessor(logger *slog.Logger) *MessageProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MessageProcessor) httpError(rw http.ResponseWriter, message string, args ...any) {
|
||||
m.Error(message, args...)
|
||||
rw.WriteHeader(http.StatusBadRequest)
|
||||
_, err := rw.Write([]byte(fmt.Sprintf(message, args...)))
|
||||
func (m *MessageProcessor) httpError(rw http.ResponseWriter, message string, err error) {
|
||||
m.Error(message, "error", err)
|
||||
rw.WriteHeader(http.StatusUnprocessableEntity)
|
||||
_, err = rw.Write([]byte(err.Error()))
|
||||
if err != nil {
|
||||
m.Error("Unable to write error message: %s", err)
|
||||
m.Error("Unable to write error response:", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,12 +62,12 @@ func (m *MessageProcessor) getTargetWindow(r *http.Request) (Window, string) {
|
||||
}
|
||||
wID, err := strconv.ParseUint(windowID, 10, 64)
|
||||
if err != nil {
|
||||
m.Error("Window ID '%s' not parsable: %s", windowID, err)
|
||||
m.Error("Window ID not parsable:", "id", windowID, "error", err)
|
||||
return nil, windowID
|
||||
}
|
||||
targetWindow := globalApplication.getWindowForID(uint(wID))
|
||||
if targetWindow == nil {
|
||||
m.Error("Window ID %d not found", wID)
|
||||
m.Error("Window ID not found:", "id", wID)
|
||||
return nil, windowID
|
||||
}
|
||||
return targetWindow, windowID
|
||||
@ -75,7 +76,7 @@ func (m *MessageProcessor) getTargetWindow(r *http.Request) (Window, string) {
|
||||
func (m *MessageProcessor) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
object := r.URL.Query().Get("object")
|
||||
if object == "" {
|
||||
m.httpError(rw, "Invalid runtime call")
|
||||
m.httpError(rw, "Invalid runtime call:", errors.New("missing object value"))
|
||||
return
|
||||
}
|
||||
|
||||
@ -90,19 +91,19 @@ func (m *MessageProcessor) HandleRuntimeCallWithIDs(rw http.ResponseWriter, r *h
|
||||
}()
|
||||
object, err := strconv.Atoi(r.URL.Query().Get("object"))
|
||||
if err != nil {
|
||||
m.httpError(rw, "Error decoding object value: "+err.Error())
|
||||
m.httpError(rw, "Invalid runtime call:", fmt.Errorf("error decoding object value: %w", err))
|
||||
return
|
||||
}
|
||||
method, err := strconv.Atoi(r.URL.Query().Get("method"))
|
||||
if err != nil {
|
||||
m.httpError(rw, "Error decoding method value: "+err.Error())
|
||||
m.httpError(rw, "Invalid runtime call:", fmt.Errorf("error decoding method value: %w", err))
|
||||
return
|
||||
}
|
||||
params := QueryParams(r.URL.Query())
|
||||
|
||||
targetWindow, nameOrID := m.getTargetWindow(r)
|
||||
if targetWindow == nil {
|
||||
m.httpError(rw, fmt.Sprintf("Window '%s' not found", nameOrID))
|
||||
m.httpError(rw, "Invalid runtime call:", fmt.Errorf("window '%s' not found", nameOrID))
|
||||
return
|
||||
}
|
||||
|
||||
@ -130,7 +131,7 @@ func (m *MessageProcessor) HandleRuntimeCallWithIDs(rw http.ResponseWriter, r *h
|
||||
case cancelCallRequesst:
|
||||
m.processCallCancelMethod(method, rw, r, targetWindow, params)
|
||||
default:
|
||||
m.httpError(rw, "Unknown runtime call: %d", object)
|
||||
m.httpError(rw, "Invalid runtime call:", fmt.Errorf("unknown object %d", object))
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,13 +151,13 @@ func (m *MessageProcessor) json(rw http.ResponseWriter, data any) {
|
||||
if data != nil {
|
||||
jsonPayload, err = json.Marshal(data)
|
||||
if err != nil {
|
||||
m.Error("Unable to convert data to JSON. Please report this to the Wails team! Error: %s", err)
|
||||
m.Error("Unable to convert data to JSON. Please report this to the Wails team!", "error", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
_, err = rw.Write(jsonPayload)
|
||||
if err != nil {
|
||||
m.Error("Unable to write json payload. Please report this to the Wails team! Error: %s", err)
|
||||
m.Error("Unable to write json payload. Please report this to the Wails team!", "error", err)
|
||||
return
|
||||
}
|
||||
m.ok(rw)
|
||||
@ -165,7 +166,7 @@ func (m *MessageProcessor) json(rw http.ResponseWriter, data any) {
|
||||
func (m *MessageProcessor) text(rw http.ResponseWriter, data string) {
|
||||
_, err := rw.Write([]byte(data))
|
||||
if err != nil {
|
||||
m.Error("Unable to write json payload. Please report this to the Wails team! Error: %s", err)
|
||||
m.Error("Unable to write json payload. Please report this to the Wails team!", "error", err)
|
||||
return
|
||||
}
|
||||
rw.Header().Set("Content-Type", "text/plain")
|
||||
|
@ -1,6 +1,7 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@ -28,9 +29,9 @@ func (m *MessageProcessor) processApplicationMethod(method int, rw http.Response
|
||||
globalApplication.Show()
|
||||
m.ok(rw)
|
||||
default:
|
||||
m.httpError(rw, "Unknown application method: %d", method)
|
||||
m.httpError(rw, "Invalid application call:", fmt.Errorf("unknown method: %d", method))
|
||||
return
|
||||
}
|
||||
|
||||
m.Info("Runtime Call:", "method", "Application."+applicationMethodNames[method])
|
||||
|
||||
m.Info("Runtime call:", "method", "Application."+applicationMethodNames[method])
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"github.com/pkg/browser"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/browser"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -14,10 +17,9 @@ var browserMethods = map[int]string{
|
||||
}
|
||||
|
||||
func (m *MessageProcessor) processBrowserMethod(method int, rw http.ResponseWriter, _ *http.Request, _ Window, params QueryParams) {
|
||||
|
||||
args, err := params.Args()
|
||||
if err != nil {
|
||||
m.httpError(rw, "Unable to parse arguments: %s", err.Error())
|
||||
m.httpError(rw, "Invalid browser call:", fmt.Errorf("unable to parse arguments: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -25,19 +27,20 @@ func (m *MessageProcessor) processBrowserMethod(method int, rw http.ResponseWrit
|
||||
case BrowserOpenURL:
|
||||
url := args.String("url")
|
||||
if url == nil {
|
||||
m.Error("OpenURL: url is required")
|
||||
m.httpError(rw, "Invalid browser call:", errors.New("missing argument 'url'"))
|
||||
return
|
||||
}
|
||||
|
||||
err := browser.OpenURL(*url)
|
||||
if err != nil {
|
||||
m.Error("OpenURL: %s", err.Error())
|
||||
m.httpError(rw, "OpenURL failed:", err)
|
||||
return
|
||||
}
|
||||
|
||||
m.ok(rw)
|
||||
m.Info("Runtime Call:", "method", "Browser."+browserMethods[method], "url", *url)
|
||||
m.Info("Runtime call:", "method", "Browser."+browserMethods[method], "url", *url)
|
||||
default:
|
||||
m.httpError(rw, "Unknown browser method: %d", method)
|
||||
m.httpError(rw, "Invalid browser call:", fmt.Errorf("unknown method: %d", method))
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package application
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
@ -15,33 +16,46 @@ const (
|
||||
)
|
||||
|
||||
func (m *MessageProcessor) callErrorCallback(window Window, message string, callID *string, err error) {
|
||||
errorMsg := fmt.Sprintf(message, err)
|
||||
m.Error(errorMsg)
|
||||
window.CallError(*callID, errorMsg)
|
||||
m.Error(message, "id", *callID, "error", err)
|
||||
if cerr := (*CallError)(nil); errors.As(err, &cerr) {
|
||||
if data, jsonErr := json.Marshal(cerr); jsonErr == nil {
|
||||
window.CallError(*callID, string(data), true)
|
||||
return
|
||||
} else {
|
||||
m.Error("Unable to convert data to JSON. Please report this to the Wails team!", "id", *callID, "error", jsonErr)
|
||||
}
|
||||
}
|
||||
|
||||
window.CallError(*callID, err.Error(), false)
|
||||
}
|
||||
|
||||
func (m *MessageProcessor) callCallback(window Window, callID *string, result string, isJSON bool) {
|
||||
func (m *MessageProcessor) callCallback(window Window, callID *string, result string) {
|
||||
window.CallResponse(*callID, result)
|
||||
}
|
||||
|
||||
func (m *MessageProcessor) processCallCancelMethod(method int, rw http.ResponseWriter, r *http.Request, window Window, params QueryParams) {
|
||||
args, err := params.Args()
|
||||
if err != nil {
|
||||
m.httpError(rw, "Unable to parse arguments: %s", err.Error())
|
||||
return
|
||||
}
|
||||
callID := args.String("call-id")
|
||||
if callID == nil || *callID == "" {
|
||||
m.Error("call-id is required")
|
||||
m.httpError(rw, "Invalid binding call:", fmt.Errorf("unable to parse arguments: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
m.l.Lock()
|
||||
cancel := m.runningCalls[*callID]
|
||||
m.l.Unlock()
|
||||
callID := args.String("call-id")
|
||||
if callID == nil || *callID == "" {
|
||||
m.httpError(rw, "Invalid binding call:", errors.New("missing argument 'call-id'"))
|
||||
return
|
||||
}
|
||||
|
||||
var cancel func()
|
||||
func() {
|
||||
m.l.Lock()
|
||||
defer m.l.Unlock()
|
||||
cancel = m.runningCalls[*callID]
|
||||
}()
|
||||
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
m.Info("Binding call canceled:", "id", *callID)
|
||||
}
|
||||
m.ok(rw)
|
||||
}
|
||||
@ -49,12 +63,13 @@ func (m *MessageProcessor) processCallCancelMethod(method int, rw http.ResponseW
|
||||
func (m *MessageProcessor) processCallMethod(method int, rw http.ResponseWriter, r *http.Request, window Window, params QueryParams) {
|
||||
args, err := params.Args()
|
||||
if err != nil {
|
||||
m.httpError(rw, "Unable to parse arguments: %s", err.Error())
|
||||
m.httpError(rw, "Invalid binding call:", fmt.Errorf("unable to parse arguments: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
callID := args.String("call-id")
|
||||
if callID == nil || *callID == "" {
|
||||
m.Error("call-id is required")
|
||||
m.httpError(rw, "Invalid binding call:", errors.New("missing argument 'call-id'"))
|
||||
return
|
||||
}
|
||||
|
||||
@ -63,86 +78,116 @@ func (m *MessageProcessor) processCallMethod(method int, rw http.ResponseWriter,
|
||||
var options CallOptions
|
||||
err := params.ToStruct(&options)
|
||||
if err != nil {
|
||||
m.callErrorCallback(window, "Error parsing call options: %s", callID, err)
|
||||
return
|
||||
}
|
||||
var boundMethod *BoundMethod
|
||||
if options.MethodName != "" {
|
||||
boundMethod = globalApplication.bindings.Get(&options)
|
||||
if boundMethod == nil {
|
||||
m.callErrorCallback(window, "Error getting binding for method: %s", callID, fmt.Errorf("method '%s' not found", options.MethodName))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
boundMethod = globalApplication.bindings.GetByID(options.MethodID)
|
||||
}
|
||||
if boundMethod == nil {
|
||||
m.callErrorCallback(window, "Error getting binding for method: %s", callID, fmt.Errorf("method ID %d not found", options.MethodID))
|
||||
m.httpError(rw, "Invalid binding call:", fmt.Errorf("error parsing call options: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.WithoutCancel(r.Context()))
|
||||
|
||||
// Schedule cancel in case panics happen before starting the call.
|
||||
cancelRequired := true
|
||||
defer func() {
|
||||
if cancelRequired {
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
ambiguousID := false
|
||||
m.l.Lock()
|
||||
if m.runningCalls[*callID] != nil {
|
||||
ambiguousID = true
|
||||
} else {
|
||||
m.runningCalls[*callID] = cancel
|
||||
}
|
||||
m.l.Unlock()
|
||||
func() {
|
||||
m.l.Lock()
|
||||
defer m.l.Unlock()
|
||||
|
||||
if m.runningCalls[*callID] != nil {
|
||||
ambiguousID = true
|
||||
} else {
|
||||
m.runningCalls[*callID] = cancel
|
||||
}
|
||||
}()
|
||||
|
||||
if ambiguousID {
|
||||
cancel()
|
||||
m.callErrorCallback(window, "Error calling method: %s, a method call with the same id is already running", callID, err)
|
||||
m.httpError(rw, "Invalid binding call:", fmt.Errorf("ambiguous call id: %s", *callID))
|
||||
return
|
||||
}
|
||||
|
||||
// Set the context values for the window
|
||||
if window != nil {
|
||||
ctx = context.WithValue(ctx, WindowKey, window)
|
||||
m.ok(rw) // From now on, failures are reported through the error callback.
|
||||
|
||||
// Log call
|
||||
var methodRef any = options.MethodName
|
||||
if options.MethodName == "" {
|
||||
methodRef = options.MethodID
|
||||
}
|
||||
m.Info("Binding call started:", "id", *callID, "method", methodRef)
|
||||
|
||||
go func() {
|
||||
defer handlePanic()
|
||||
defer func() {
|
||||
cancel()
|
||||
|
||||
m.l.Lock()
|
||||
defer m.l.Unlock()
|
||||
delete(m.runningCalls, *callID)
|
||||
m.l.Unlock()
|
||||
}()
|
||||
defer cancel()
|
||||
|
||||
var boundMethod *BoundMethod
|
||||
if options.MethodName != "" {
|
||||
boundMethod = globalApplication.bindings.Get(&options)
|
||||
if boundMethod == nil {
|
||||
m.callErrorCallback(window, "Binding call failed:", callID, &CallError{
|
||||
Kind: ReferenceError,
|
||||
Message: fmt.Sprintf("unknown bound method name '%s'", options.MethodName),
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
boundMethod = globalApplication.bindings.GetByID(options.MethodID)
|
||||
if boundMethod == nil {
|
||||
m.callErrorCallback(window, "Binding call failed:", callID, &CallError{
|
||||
Kind: ReferenceError,
|
||||
Message: fmt.Sprintf("unknown bound method id %d", options.MethodID),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare args for logging. This should never fail since json.Unmarshal succeeded before.
|
||||
jsonArgs, _ := json.Marshal(options.Args)
|
||||
var jsonResult []byte
|
||||
defer m.Info("Binding call complete:", "id", *callID, "method", boundMethod, "args", string(jsonArgs), "result", string(jsonResult))
|
||||
|
||||
// Set the context values for the window
|
||||
if window != nil {
|
||||
ctx = context.WithValue(ctx, WindowKey, window)
|
||||
}
|
||||
|
||||
result, err := boundMethod.Call(ctx, options.Args)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Error calling method '%v'", boundMethod.Name)
|
||||
m.callErrorCallback(window, msg+": %s", callID, err)
|
||||
if cerr := (*CallError)(nil); errors.As(err, &cerr) {
|
||||
switch cerr.Kind {
|
||||
case ReferenceError, TypeError:
|
||||
m.callErrorCallback(window, "Binding call failed:", callID, cerr)
|
||||
case RuntimeError:
|
||||
m.callErrorCallback(window, "Bound method returned an error:", callID, cerr)
|
||||
}
|
||||
return
|
||||
}
|
||||
var jsonResult = []byte("{}")
|
||||
|
||||
if result != nil {
|
||||
// convert result to json
|
||||
jsonResult, err = json.Marshal(result)
|
||||
if err != nil {
|
||||
m.callErrorCallback(window, "Error converting result to json: %s", callID, err)
|
||||
m.callErrorCallback(window, "Binding call failed:", callID, &CallError{
|
||||
Kind: TypeError,
|
||||
Message: fmt.Sprintf("error marshaling result: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
m.callCallback(window, callID, string(jsonResult), true)
|
||||
|
||||
var jsonArgs struct {
|
||||
Args json.RawMessage `json:"args"`
|
||||
}
|
||||
err = params.ToStruct(&jsonArgs)
|
||||
if err != nil {
|
||||
m.callErrorCallback(window, "Error parsing arguments: %s", callID, err)
|
||||
return
|
||||
}
|
||||
m.Info("Call Binding:", "method", boundMethod, "args", string(jsonArgs.Args), "result", result)
|
||||
m.callCallback(window, callID, string(jsonResult))
|
||||
}()
|
||||
m.ok(rw)
|
||||
default:
|
||||
m.httpError(rw, "Unknown call method: %d", method)
|
||||
}
|
||||
|
||||
cancelRequired = false
|
||||
|
||||
default:
|
||||
m.httpError(rw, "Invalid binding call:", fmt.Errorf("unknown method: %d", method))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@ -15,30 +17,31 @@ var clipboardMethods = map[int]string{
|
||||
}
|
||||
|
||||
func (m *MessageProcessor) processClipboardMethod(method int, rw http.ResponseWriter, _ *http.Request, _ Window, params QueryParams) {
|
||||
|
||||
args, err := params.Args()
|
||||
if err != nil {
|
||||
m.httpError(rw, "Unable to parse arguments: %s", err.Error())
|
||||
m.httpError(rw, "Invalid clipboard call:", fmt.Errorf("unable to parse arguments: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
var text string
|
||||
|
||||
switch method {
|
||||
case ClipboardSetText:
|
||||
text := args.String("text")
|
||||
if text == nil {
|
||||
m.Error("SetText: text is required")
|
||||
textp := args.String("text")
|
||||
if textp == nil {
|
||||
m.httpError(rw, "Invalid clipboard call:", errors.New("missing argument 'text'"))
|
||||
return
|
||||
}
|
||||
globalApplication.Clipboard().SetText(*text)
|
||||
text = *textp
|
||||
globalApplication.Clipboard().SetText(text)
|
||||
m.ok(rw)
|
||||
m.Info("Runtime Call:", "method", "Clipboard."+clipboardMethods[method], "text", *text)
|
||||
case ClipboardText:
|
||||
text, _ := globalApplication.Clipboard().Text()
|
||||
text, _ = globalApplication.Clipboard().Text()
|
||||
m.text(rw, text)
|
||||
m.Info("Runtime Call:", "method", "Clipboard."+clipboardMethods[method], "text", text)
|
||||
default:
|
||||
m.httpError(rw, "Unknown clipboard method: %d", method)
|
||||
m.httpError(rw, "Invalid clipboard call:", fmt.Errorf("unknown method: %d", method))
|
||||
return
|
||||
}
|
||||
|
||||
m.Info("Runtime call:", "method", "Clipboard."+clipboardMethods[method], "text", text)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@ -29,21 +30,21 @@ var contextmenuMethodNames = map[int]string{
|
||||
}
|
||||
|
||||
func (m *MessageProcessor) processContextMenuMethod(method int, rw http.ResponseWriter, _ *http.Request, window Window, params QueryParams) {
|
||||
|
||||
switch method {
|
||||
case ContextMenuOpen:
|
||||
var data ContextMenuData
|
||||
err := params.ToStruct(&data)
|
||||
if err != nil {
|
||||
m.httpError(rw, "error parsing contextmenu message: %s", err.Error())
|
||||
m.httpError(rw, "Invalid contextmenu call:", fmt.Errorf("error parsing parameters: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
window.OpenContextMenu(&data)
|
||||
|
||||
m.ok(rw)
|
||||
m.Info("Runtime call:", "method", "ContextMenu."+contextmenuMethodNames[method], "id", data.Id, "x", data.X, "y", data.Y, "data", data.Data)
|
||||
default:
|
||||
m.httpError(rw, "Unknown contextmenu method: %d", method)
|
||||
m.httpError(rw, "Invalid contextmenu call:", fmt.Errorf("unknown method: %d", method))
|
||||
return
|
||||
}
|
||||
|
||||
m.Info("Runtime Call:", "method", "ContextMenu."+contextmenuMethodNames[method])
|
||||
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package application
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
@ -26,9 +27,8 @@ var dialogMethodNames = map[int]string{
|
||||
}
|
||||
|
||||
func (m *MessageProcessor) dialogErrorCallback(window Window, message string, dialogID *string, err error) {
|
||||
errorMsg := fmt.Sprintf(message, err)
|
||||
m.Error(errorMsg)
|
||||
window.DialogError(*dialogID, errorMsg)
|
||||
m.Error(message, "error", err)
|
||||
window.DialogError(*dialogID, err.Error())
|
||||
}
|
||||
|
||||
func (m *MessageProcessor) dialogCallback(window Window, dialogID *string, result string, isJSON bool) {
|
||||
@ -36,15 +36,15 @@ func (m *MessageProcessor) dialogCallback(window Window, dialogID *string, resul
|
||||
}
|
||||
|
||||
func (m *MessageProcessor) processDialogMethod(method int, rw http.ResponseWriter, r *http.Request, window Window, params QueryParams) {
|
||||
|
||||
args, err := params.Args()
|
||||
if err != nil {
|
||||
m.httpError(rw, "Unable to parse arguments: %s", err.Error())
|
||||
m.httpError(rw, "Invalid dialog call:", fmt.Errorf("unable to parse arguments: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
dialogID := args.String("dialog-id")
|
||||
if dialogID == nil {
|
||||
m.Error("dialog-id is required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing argument 'dialog-id'"))
|
||||
return
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ func (m *MessageProcessor) processDialogMethod(method int, rw http.ResponseWrite
|
||||
var options MessageDialogOptions
|
||||
err := params.ToStruct(&options)
|
||||
if err != nil {
|
||||
m.dialogErrorCallback(window, "Error parsing dialog options: %s", dialogID, err)
|
||||
m.httpError(rw, "Invalid dialog call:", fmt.Errorf("error parsing dialog options: %w", err))
|
||||
return
|
||||
}
|
||||
if len(options.Buttons) == 0 {
|
||||
@ -91,13 +91,13 @@ func (m *MessageProcessor) processDialogMethod(method int, rw http.ResponseWrite
|
||||
dialog.AddButtons(options.Buttons)
|
||||
dialog.Show()
|
||||
m.ok(rw)
|
||||
m.Info("Runtime Call:", "method", methodName, "options", options)
|
||||
m.Info("Runtime call:", "method", methodName, "options", options)
|
||||
|
||||
case DialogOpenFile:
|
||||
var options OpenFileDialogOptions
|
||||
err := params.ToStruct(&options)
|
||||
if err != nil {
|
||||
m.httpError(rw, "Error parsing dialog options: %s", err.Error())
|
||||
m.httpError(rw, "Invalid dialog call:", fmt.Errorf("error parsing dialog options: %w", err))
|
||||
return
|
||||
}
|
||||
var detached = args.Bool("Detached")
|
||||
@ -111,35 +111,35 @@ func (m *MessageProcessor) processDialogMethod(method int, rw http.ResponseWrite
|
||||
if options.AllowsMultipleSelection {
|
||||
files, err := dialog.PromptForMultipleSelection()
|
||||
if err != nil {
|
||||
m.dialogErrorCallback(window, "Error getting selection: %s", dialogID, err)
|
||||
m.dialogErrorCallback(window, "Dialog.OpenFile failed", dialogID, fmt.Errorf("error getting selection: %w", err))
|
||||
return
|
||||
} else {
|
||||
result, err := json.Marshal(files)
|
||||
if err != nil {
|
||||
m.dialogErrorCallback(window, "Error marshalling files: %s", dialogID, err)
|
||||
m.dialogErrorCallback(window, "Dialog.OpenFile failed", dialogID, fmt.Errorf("error marshaling files: %w", err))
|
||||
return
|
||||
}
|
||||
m.dialogCallback(window, dialogID, string(result), true)
|
||||
m.Info("Runtime Call:", "method", methodName, "result", result)
|
||||
m.Info("Runtime call:", "method", methodName, "result", result)
|
||||
}
|
||||
} else {
|
||||
file, err := dialog.PromptForSingleSelection()
|
||||
if err != nil {
|
||||
m.dialogErrorCallback(window, "Error getting selection: %s", dialogID, err)
|
||||
m.dialogErrorCallback(window, "Dialog.OpenFile failed", dialogID, fmt.Errorf("error getting selection: %w", err))
|
||||
return
|
||||
}
|
||||
m.dialogCallback(window, dialogID, file, false)
|
||||
m.Info("Runtime Call:", "method", methodName, "result", file)
|
||||
m.Info("Runtime call:", "method", methodName, "result", file)
|
||||
}
|
||||
}()
|
||||
m.ok(rw)
|
||||
m.Info("Runtime Call:", "method", methodName, "options", options)
|
||||
m.Info("Runtime call:", "method", methodName, "options", options)
|
||||
|
||||
case DialogSaveFile:
|
||||
var options SaveFileDialogOptions
|
||||
err := params.ToStruct(&options)
|
||||
if err != nil {
|
||||
m.httpError(rw, "Error parsing dialog options: %s", err.Error())
|
||||
m.httpError(rw, "Invalid dialog call:", fmt.Errorf("error parsing dialog options: %w", err))
|
||||
return
|
||||
}
|
||||
var detached = args.Bool("Detached")
|
||||
@ -152,17 +152,17 @@ func (m *MessageProcessor) processDialogMethod(method int, rw http.ResponseWrite
|
||||
defer handlePanic()
|
||||
file, err := dialog.PromptForSingleSelection()
|
||||
if err != nil {
|
||||
m.dialogErrorCallback(window, "Error getting selection: %s", dialogID, err)
|
||||
m.dialogErrorCallback(window, "Dialog.SaveFile failed", dialogID, fmt.Errorf("error getting selection: %w", err))
|
||||
return
|
||||
}
|
||||
m.dialogCallback(window, dialogID, file, false)
|
||||
m.Info("Runtime Call:", "method", methodName, "result", file)
|
||||
m.Info("Runtime call:", "method", methodName, "result", file)
|
||||
}()
|
||||
m.ok(rw)
|
||||
m.Info("Runtime Call:", "method", methodName, "options", options)
|
||||
m.Info("Runtime call:", "method", methodName, "options", options)
|
||||
|
||||
default:
|
||||
m.httpError(rw, "Unknown dialog method: %d", method)
|
||||
m.httpError(rw, "Invalid dialog call:", fmt.Errorf("unknown method: %d", method))
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -13,28 +16,26 @@ var eventsMethodNames = map[int]string{
|
||||
}
|
||||
|
||||
func (m *MessageProcessor) processEventsMethod(method int, rw http.ResponseWriter, _ *http.Request, window Window, params QueryParams) {
|
||||
|
||||
var event CustomEvent
|
||||
|
||||
switch method {
|
||||
case EventsEmit:
|
||||
var event CustomEvent
|
||||
err := params.ToStruct(&event)
|
||||
if err != nil {
|
||||
m.httpError(rw, "Error parsing event: %s", err.Error())
|
||||
m.httpError(rw, "Invalid events call:", fmt.Errorf("error parsing event: %w", err))
|
||||
return
|
||||
}
|
||||
if event.Name == "" {
|
||||
m.httpError(rw, "Event name must be specified")
|
||||
m.httpError(rw, "Invalid events call:", errors.New("missing event name"))
|
||||
return
|
||||
}
|
||||
|
||||
event.Sender = window.Name()
|
||||
globalApplication.customEventProcessor.Emit(&event)
|
||||
|
||||
m.ok(rw)
|
||||
m.Info("Runtime call:", "method", "Events."+eventsMethodNames[method], "name", event.Name, "sender", event.Sender, "data", event.Data, "cancelled", event.IsCancelled())
|
||||
default:
|
||||
m.httpError(rw, "Unknown event method: %d", method)
|
||||
m.httpError(rw, "Invalid events call:", fmt.Errorf("unknown method: %d", method))
|
||||
return
|
||||
}
|
||||
|
||||
m.Info("Runtime Call:", "method", "Events."+eventsMethodNames[method], "name", event.Name, "sender", event.Sender, "data", event.Data, "cancelled", event.IsCancelled())
|
||||
|
||||
}
|
||||
|
@ -141,6 +141,8 @@ func convertNumber[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16
|
||||
result = v
|
||||
case float64:
|
||||
result = T(v)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return &result
|
||||
}
|
||||
@ -154,6 +156,7 @@ func (a *Args) UInt8(s string) *uint8 {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Args) UInt(s string) *uint {
|
||||
if a == nil {
|
||||
return nil
|
||||
@ -169,8 +172,9 @@ func (a *Args) Float64(s string) *float64 {
|
||||
return nil
|
||||
}
|
||||
if val := a.data[s]; val != nil {
|
||||
result := val.(float64)
|
||||
return &result
|
||||
if result, ok := val.(float64); ok {
|
||||
return &result
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -180,8 +184,9 @@ func (a *Args) Bool(s string) *bool {
|
||||
return nil
|
||||
}
|
||||
if val := a.data[s]; val != nil {
|
||||
result := val.(bool)
|
||||
return &result
|
||||
if result, ok := val.(bool); ok {
|
||||
return &result
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@ -17,33 +18,33 @@ var screensMethodNames = map[int]string{
|
||||
}
|
||||
|
||||
func (m *MessageProcessor) processScreensMethod(method int, rw http.ResponseWriter, _ *http.Request, _ Window, _ QueryParams) {
|
||||
|
||||
switch method {
|
||||
case ScreensGetAll:
|
||||
screens, err := globalApplication.GetScreens()
|
||||
if err != nil {
|
||||
m.Error("GetAll: %s", err.Error())
|
||||
m.httpError(rw, "GetScreens failed:", err)
|
||||
return
|
||||
}
|
||||
m.json(rw, screens)
|
||||
case ScreensGetPrimary:
|
||||
screen, err := globalApplication.GetPrimaryScreen()
|
||||
if err != nil {
|
||||
m.Error("GetPrimary: %s", err.Error())
|
||||
m.httpError(rw, "GetPrimary failed:", err)
|
||||
return
|
||||
}
|
||||
m.json(rw, screen)
|
||||
case ScreensGetCurrent:
|
||||
screen, err := globalApplication.CurrentWindow().GetScreen()
|
||||
if err != nil {
|
||||
m.Error("GetCurrent: %s", err.Error())
|
||||
m.httpError(rw, "Window.GetScreen failed:", err)
|
||||
return
|
||||
}
|
||||
m.json(rw, screen)
|
||||
default:
|
||||
m.httpError(rw, "Unknown screens method: %d", method)
|
||||
m.httpError(rw, "Invalid screens call:", fmt.Errorf("unknown method: %d", method))
|
||||
return
|
||||
}
|
||||
|
||||
m.Info("Runtime Call:", "method", "Screens."+screensMethodNames[method])
|
||||
m.Info("Runtime call:", "method", "Screens."+screensMethodNames[method])
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@ -15,16 +16,15 @@ var systemMethodNames = map[int]string{
|
||||
}
|
||||
|
||||
func (m *MessageProcessor) processSystemMethod(method int, rw http.ResponseWriter, r *http.Request, window Window, params QueryParams) {
|
||||
|
||||
switch method {
|
||||
case SystemIsDarkMode:
|
||||
m.json(rw, globalApplication.IsDarkMode())
|
||||
case Environment:
|
||||
m.json(rw, globalApplication.Environment())
|
||||
default:
|
||||
m.httpError(rw, "Unknown system method: %d", method)
|
||||
m.httpError(rw, "Invalid system call:", fmt.Errorf("unknown method: %d", method))
|
||||
return
|
||||
}
|
||||
|
||||
m.Info("Runtime Call:", "method", "System."+systemMethodNames[method])
|
||||
|
||||
m.Info("Runtime call:", "method", "System."+systemMethodNames[method])
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@ -107,10 +109,9 @@ var windowMethodNames = map[int]string{
|
||||
}
|
||||
|
||||
func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWriter, _ *http.Request, window Window, params QueryParams) {
|
||||
|
||||
args, err := params.Args()
|
||||
if err != nil {
|
||||
m.httpError(rw, "Unable to parse arguments: %s", err.Error())
|
||||
m.httpError(rw, "Invalid window call:", fmt.Errorf("unable to parse arguments: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -145,7 +146,7 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
|
||||
case WindowGetScreen:
|
||||
screen, err := window.GetScreen()
|
||||
if err != nil {
|
||||
m.httpError(rw, err.Error())
|
||||
m.httpError(rw, "Window.GetScreen failed:", err)
|
||||
return
|
||||
}
|
||||
m.json(rw, screen)
|
||||
@ -197,18 +198,20 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
|
||||
case WindowSetPosition:
|
||||
x := args.Int("x")
|
||||
if x == nil {
|
||||
m.Error("Invalid SetPosition Message: 'x' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'x'"))
|
||||
return
|
||||
}
|
||||
y := args.Int("y")
|
||||
if y == nil {
|
||||
m.Error("Invalid SetPosition Message: 'y' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'y'"))
|
||||
return
|
||||
}
|
||||
window.SetPosition(*x, *y)
|
||||
m.ok(rw)
|
||||
case WindowSetAlwaysOnTop:
|
||||
alwaysOnTop := args.Bool("alwaysOnTop")
|
||||
if alwaysOnTop == nil {
|
||||
m.Error("Invalid SetAlwaysOnTop Message: 'alwaysOnTop' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'alwaysOnTop'"))
|
||||
return
|
||||
}
|
||||
window.SetAlwaysOnTop(*alwaysOnTop)
|
||||
@ -216,22 +219,22 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
|
||||
case WindowSetBackgroundColour:
|
||||
r := args.UInt8("r")
|
||||
if r == nil {
|
||||
m.Error("Invalid SetBackgroundColour Message: 'r' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'r'"))
|
||||
return
|
||||
}
|
||||
g := args.UInt8("g")
|
||||
if g == nil {
|
||||
m.Error("Invalid SetBackgroundColour Message: 'g' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'g'"))
|
||||
return
|
||||
}
|
||||
b := args.UInt8("b")
|
||||
if b == nil {
|
||||
m.Error("Invalid SetBackgroundColour Message: 'b' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'b'"))
|
||||
return
|
||||
}
|
||||
a := args.UInt8("a")
|
||||
if a == nil {
|
||||
m.Error("Invalid SetBackgroundColour Message: 'a' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'a'"))
|
||||
return
|
||||
}
|
||||
window.SetBackgroundColour(RGBA{
|
||||
@ -244,7 +247,7 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
|
||||
case WindowSetFrameless:
|
||||
frameless := args.Bool("frameless")
|
||||
if frameless == nil {
|
||||
m.Error("Invalid SetFrameless Message: 'frameless' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'frameless'"))
|
||||
return
|
||||
}
|
||||
window.SetFrameless(*frameless)
|
||||
@ -252,40 +255,46 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
|
||||
case WindowSetMaxSize:
|
||||
width := args.Int("width")
|
||||
if width == nil {
|
||||
m.Error("Invalid SetMaxSize Message: 'width' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'width'"))
|
||||
return
|
||||
}
|
||||
height := args.Int("height")
|
||||
if height == nil {
|
||||
m.Error("Invalid SetMaxSize Message: 'height' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'height'"))
|
||||
return
|
||||
}
|
||||
window.SetMaxSize(*width, *height)
|
||||
m.ok(rw)
|
||||
case WindowSetMinSize:
|
||||
width := args.Int("width")
|
||||
if width == nil {
|
||||
m.Error("Invalid SetMinSize Message: 'width' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'width'"))
|
||||
return
|
||||
}
|
||||
height := args.Int("height")
|
||||
if height == nil {
|
||||
m.Error("Invalid SetMinSize Message: 'height' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'height'"))
|
||||
return
|
||||
}
|
||||
window.SetMinSize(*width, *height)
|
||||
m.ok(rw)
|
||||
case WindowSetRelativePosition:
|
||||
x := args.Int("x")
|
||||
if x == nil {
|
||||
m.Error("Invalid SetRelativePosition Message: 'x' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'x'"))
|
||||
return
|
||||
}
|
||||
y := args.Int("y")
|
||||
if y == nil {
|
||||
m.Error("Invalid SetRelativePosition Message: 'y' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'y'"))
|
||||
return
|
||||
}
|
||||
window.SetRelativePosition(*x, *y)
|
||||
m.ok(rw)
|
||||
case WindowSetResizable:
|
||||
resizable := args.Bool("resizable")
|
||||
if resizable == nil {
|
||||
m.Error("Invalid SetResizable Message: 'resizable' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'resizable'"))
|
||||
return
|
||||
}
|
||||
window.SetResizable(*resizable)
|
||||
@ -293,18 +302,20 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
|
||||
case WindowSetSize:
|
||||
width := args.Int("width")
|
||||
if width == nil {
|
||||
m.Error("Invalid SetSize Message: 'width' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'width'"))
|
||||
return
|
||||
}
|
||||
height := args.Int("height")
|
||||
if height == nil {
|
||||
m.Error("Invalid SetSize Message: 'height' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'height'"))
|
||||
return
|
||||
}
|
||||
window.SetSize(*width, *height)
|
||||
m.ok(rw)
|
||||
case WindowSetTitle:
|
||||
title := args.String("title")
|
||||
if title == nil {
|
||||
m.Error("Invalid SetTitle Message: 'title' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing argument 'title'"))
|
||||
return
|
||||
}
|
||||
window.SetTitle(*title)
|
||||
@ -312,7 +323,7 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
|
||||
case WindowSetZoom:
|
||||
zoom := args.Float64("zoom")
|
||||
if zoom == nil {
|
||||
m.Error("Invalid SetZoom Message: 'zoom' value required")
|
||||
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'zoom'"))
|
||||
return
|
||||
}
|
||||
window.SetZoom(*zoom)
|
||||
@ -360,8 +371,9 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
|
||||
window.ZoomReset()
|
||||
m.ok(rw)
|
||||
default:
|
||||
m.httpError(rw, "Unknown window method id: %d", method)
|
||||
m.httpError(rw, "Invalid window call:", fmt.Errorf("unknown method %d", method))
|
||||
return
|
||||
}
|
||||
|
||||
m.Info("Runtime Call:", "method", "Window."+windowMethodNames[method])
|
||||
m.Info("Runtime call:", "method", "Window."+windowMethodNames[method])
|
||||
}
|
||||
|
@ -73,10 +73,8 @@ func handlePanic(options ...handlePanicOptions) bool {
|
||||
}
|
||||
|
||||
// Get the error
|
||||
var err error
|
||||
if errPanic, ok := e.(error); ok {
|
||||
err = errPanic
|
||||
} else {
|
||||
err, ok := e.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("%v", e)
|
||||
}
|
||||
|
||||
@ -102,6 +100,5 @@ func processPanic(panicDetails *PanicDetails) {
|
||||
}
|
||||
|
||||
func defaultPanicHandler(panicDetails *PanicDetails) {
|
||||
errorMessage := fmt.Sprintf("panic error: %s\n%s", panicDetails.Error.Error(), panicDetails.StackTrace)
|
||||
globalApplication.fatal(errorMessage)
|
||||
globalApplication.fatal("panic error: %w\n%s", panicDetails.Error, panicDetails.StackTrace)
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v3/pkg/w32"
|
||||
)
|
||||
|
||||
@ -133,12 +132,12 @@ func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) {
|
||||
}
|
||||
ok := w32.AppendMenu(parentMenu, flags, uintptr(itemID), w32.MustStringToUTF16Ptr(menuText))
|
||||
if !ok {
|
||||
globalApplication.fatal(fmt.Sprintf("Error adding menu item: %s", menuText))
|
||||
globalApplication.fatal("error adding menu item '%s'", menuText)
|
||||
}
|
||||
if item.bitmap != nil {
|
||||
err := w32.SetMenuIcons(parentMenu, itemID, item.bitmap, nil)
|
||||
if err != nil {
|
||||
globalApplication.fatal(fmt.Sprintf("Error setting menu icons: %s", err.Error()))
|
||||
globalApplication.fatal("error setting menu icons: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/w32"
|
||||
@ -80,7 +80,7 @@ func getScreenForWindowHwnd(hwnd w32.HWND) (*Screen, error) {
|
||||
return screen, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("screen not found for window")
|
||||
return nil, errors.New("screen not found for window")
|
||||
}
|
||||
|
||||
func hMonitorToScreenID(hMonitor uintptr) string {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
@ -363,7 +363,7 @@ func (s *Screen) physicalToDipRect(physicalRect Rect) Rect {
|
||||
// for future coordinate transformation between the physical and logical (DIP) space
|
||||
func (m *ScreenManager) LayoutScreens(screens []*Screen) error {
|
||||
if screens == nil || len(screens) == 0 {
|
||||
return fmt.Errorf("screens parameter is nil or empty")
|
||||
return errors.New("screens parameter is nil or empty")
|
||||
}
|
||||
m.screens = screens
|
||||
|
||||
@ -397,9 +397,9 @@ func (m *ScreenManager) calculateScreensDipCoordinates() error {
|
||||
}
|
||||
}
|
||||
if m.primaryScreen == nil {
|
||||
return fmt.Errorf("no primary screen found")
|
||||
return errors.New("no primary screen found")
|
||||
} else if len(remainingScreens) != len(m.screens)-1 {
|
||||
return fmt.Errorf("invalid primary screen found")
|
||||
return errors.New("invalid primary screen found")
|
||||
}
|
||||
|
||||
// Build screens tree using the primary screen as root
|
||||
|
@ -27,6 +27,16 @@ type ServiceOptions struct {
|
||||
// it will be mounted on the internal asset server
|
||||
// at the prefix specified by Route.
|
||||
Route string
|
||||
|
||||
// MarshalError will be called if non-nil
|
||||
// to marshal to JSON the error values returned by this service's methods.
|
||||
//
|
||||
// MarshalError is not allowed to fail,
|
||||
// but it may return a nil slice to fall back
|
||||
// to the globally configured error handler.
|
||||
//
|
||||
// If the returned slice is not nil, it must contain valid JSON.
|
||||
MarshalError func(error) []byte
|
||||
}
|
||||
|
||||
// DefaultServiceOptions specifies the default values of service options,
|
||||
@ -72,8 +82,17 @@ type ServiceName interface {
|
||||
// The context will be valid as long as the application is running,
|
||||
// and will be canceled right before shutdown.
|
||||
//
|
||||
// If the return value is non-nil, it is logged along with the service name,
|
||||
// the startup process aborts and the application quits.
|
||||
// Services are guaranteed to receive the startup notification
|
||||
// in the exact order in which they were either
|
||||
// listed in [Options.Services] or registered with [App.RegisterService],
|
||||
// with those from [Options.Services] coming first.
|
||||
//
|
||||
// If the return value is non-nil, the startup process aborts
|
||||
// and [App.Run] returns the error wrapped with [fmt.Errorf]
|
||||
// in a user-friendly message comprising the service name.
|
||||
// The original error can be retrieved either by calling the Unwrap method
|
||||
// or through the [errors.As] API.
|
||||
//
|
||||
// When that happens, service instances that have been already initialised
|
||||
// receive a shutdown notification.
|
||||
type ServiceStartup interface {
|
||||
@ -83,17 +102,33 @@ type ServiceStartup interface {
|
||||
// ServiceShutdown is an *optional* method that may be implemented by service instances.
|
||||
//
|
||||
// This method will be called during application shutdown. It can be used for cleaning up resources.
|
||||
// If a service has received a startup notification,
|
||||
// then it is guaranteed to receive a shutdown notification too,
|
||||
// except in case of unhandled panics during shutdown.
|
||||
//
|
||||
// If the return value is non-nil, it is logged along with the service name.
|
||||
// Services receive shutdown notifications in reverse registration order,
|
||||
// after all user-provided shutdown hooks have run (see [App.OnShutdown]).
|
||||
//
|
||||
// If the return value is non-nil, it is passed to the application's
|
||||
// configured error handler at [Options.ErrorHandler],
|
||||
// wrapped with [fmt.Errorf] in a user-friendly message comprising the service name.
|
||||
// The default behaviour is to log the error along with the service name.
|
||||
// The original error can be retrieved either by calling the Unwrap method
|
||||
// or through the [errors.As] API.
|
||||
type ServiceShutdown interface {
|
||||
ServiceShutdown() error
|
||||
}
|
||||
|
||||
func getServiceName(service any) string {
|
||||
// First check it conforms to ServiceName interface
|
||||
if serviceName, ok := service.(ServiceName); ok {
|
||||
return serviceName.ServiceName()
|
||||
func getServiceName(service Service) string {
|
||||
if service.options.Name != "" {
|
||||
return service.options.Name
|
||||
}
|
||||
// Next, get the name from the type
|
||||
return reflect.TypeOf(service).String()
|
||||
|
||||
// Check if the service implements the ServiceName interface
|
||||
if s, ok := service.Instance().(ServiceName); ok {
|
||||
return s.ServiceName()
|
||||
}
|
||||
|
||||
// Finally, get the name from the type.
|
||||
return reflect.TypeOf(service.Instance()).Elem().String()
|
||||
}
|
||||
|
@ -3,12 +3,13 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/godbus/dbus/v5"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
type dbusHandler func(string)
|
||||
@ -36,7 +37,7 @@ func newPlatformLock(manager *singleInstanceManager) (platformLock, error) {
|
||||
|
||||
func (l *linuxLock) acquire(uniqueID string) error {
|
||||
if uniqueID == "" {
|
||||
return fmt.Errorf("UniqueID is required for single instance lock")
|
||||
return errors.New("UniqueID is required for single instance lock")
|
||||
}
|
||||
|
||||
id := "wails_app_" + strings.ReplaceAll(strings.ReplaceAll(uniqueID, "-", "_"), ".", "_")
|
||||
@ -56,11 +57,11 @@ func (l *linuxLock) acquire(uniqueID string) error {
|
||||
secondInstanceBuffer <- message
|
||||
})
|
||||
|
||||
err := conn.Export(f, dbus.ObjectPath(l.dbusPath), l.dbusName)
|
||||
if err != nil {
|
||||
globalApplication.error(err.Error())
|
||||
}
|
||||
err = conn.Export(f, dbus.ObjectPath(l.dbusPath), l.dbusName)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reply, err := conn.RequestName(l.dbusName, dbus.NameFlagDoNotQueue)
|
||||
if err != nil {
|
||||
|
@ -4,11 +4,11 @@ package application
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v3/pkg/w32"
|
||||
"golang.org/x/sys/windows"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/w32"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -33,7 +33,7 @@ func newPlatformLock(manager *singleInstanceManager) (platformLock, error) {
|
||||
|
||||
func (l *windowsLock) acquire(uniqueID string) error {
|
||||
if uniqueID == "" {
|
||||
return fmt.Errorf("UniqueID is required for single instance lock")
|
||||
return errors.New("UniqueID is required for single instance lock")
|
||||
}
|
||||
|
||||
l.uniqueID = uniqueID
|
||||
|
@ -1,7 +1,7 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
@ -123,7 +123,7 @@ func (s *SystemTray) Run() {
|
||||
|
||||
func (s *SystemTray) PositionWindow(window *WebviewWindow, offset int) error {
|
||||
if s.impl == nil {
|
||||
return fmt.Errorf("system tray not running")
|
||||
return errors.New("system tray not running")
|
||||
}
|
||||
return InvokeSyncWithError(func() error {
|
||||
return s.impl.positionWindow(window, offset)
|
||||
|
@ -30,9 +30,9 @@ static void systemTrayHide(void* nsStatusItem) {
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"fmt"
|
||||
"github.com/leaanthony/go-ansi-parser"
|
||||
)
|
||||
|
||||
@ -125,7 +125,7 @@ func (s *macosSystemTray) getScreen() (*Screen, error) {
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no screen available")
|
||||
return nil, errors.New("no screen available")
|
||||
}
|
||||
|
||||
func (s *macosSystemTray) bounds() (*Rect, error) {
|
||||
|
@ -9,13 +9,14 @@ package application
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/godbus/dbus/v5/introspect"
|
||||
"github.com/godbus/dbus/v5/prop"
|
||||
"github.com/wailsapp/wails/v3/internal/dbus/menu"
|
||||
"github.com/wailsapp/wails/v3/internal/dbus/notifier"
|
||||
"github.com/wailsapp/wails/v3/pkg/icons"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -178,7 +179,7 @@ func (s *linuxSystemTray) refresh() {
|
||||
s.menuVersion++
|
||||
if err := s.menuProps.Set("com.canonical.dbusmenu", "Version",
|
||||
dbus.MakeVariant(s.menuVersion)); err != nil {
|
||||
globalApplication.error("systray error: failed to update menu version: %v", err)
|
||||
globalApplication.error("systray error: failed to update menu version: %w", err)
|
||||
return
|
||||
}
|
||||
if err := menu.Emit(s.conn, &menu.Dbusmenu_LayoutUpdatedSignal{
|
||||
@ -187,7 +188,7 @@ func (s *linuxSystemTray) refresh() {
|
||||
Revision: s.menuVersion,
|
||||
},
|
||||
}); err != nil {
|
||||
globalApplication.error("systray error: failed to emit layout updated signal: %v", err)
|
||||
globalApplication.error("systray error: failed to emit layout updated signal: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,34 +271,34 @@ func (s *linuxSystemTray) bounds() (*Rect, error) {
|
||||
func (s *linuxSystemTray) run() {
|
||||
conn, err := dbus.SessionBus()
|
||||
if err != nil {
|
||||
globalApplication.error("systray error: failed to connect to DBus: %v\n", err)
|
||||
globalApplication.error("systray error: failed to connect to DBus: %w\n", err)
|
||||
return
|
||||
}
|
||||
err = notifier.ExportStatusNotifierItem(conn, itemPath, s)
|
||||
if err != nil {
|
||||
globalApplication.error("systray error: failed to export status notifier item: %v\n", err)
|
||||
globalApplication.error("systray error: failed to export status notifier item: %w\n", err)
|
||||
}
|
||||
|
||||
err = menu.ExportDbusmenu(conn, menuPath, s)
|
||||
if err != nil {
|
||||
globalApplication.error("systray error: failed to export status notifier menu: %v", err)
|
||||
globalApplication.error("systray error: failed to export status notifier menu: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("org.kde.StatusNotifierItem-%d-1", os.Getpid()) // register id 1 for this process
|
||||
_, err = conn.RequestName(name, dbus.NameFlagDoNotQueue)
|
||||
if err != nil {
|
||||
globalApplication.error("systray error: failed to request name: %s\n", err)
|
||||
globalApplication.error("systray error: failed to request name: %w", err)
|
||||
// it's not critical error: continue
|
||||
}
|
||||
props, err := prop.Export(conn, itemPath, s.createPropSpec())
|
||||
if err != nil {
|
||||
globalApplication.error("systray error: failed to export notifier item properties to bus: %s\n", err)
|
||||
globalApplication.error("systray error: failed to export notifier item properties to bus: %w", err)
|
||||
return
|
||||
}
|
||||
menuProps, err := prop.Export(conn, menuPath, s.createMenuPropSpec())
|
||||
if err != nil {
|
||||
globalApplication.error("systray error: failed to export notifier menu properties to bus: %s\n", err)
|
||||
globalApplication.error("systray error: failed to export notifier menu properties to bus: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -315,7 +316,7 @@ func (s *linuxSystemTray) run() {
|
||||
}
|
||||
err = conn.Export(introspect.NewIntrospectable(&node), itemPath, "org.freedesktop.DBus.Introspectable")
|
||||
if err != nil {
|
||||
globalApplication.error("systray error: failed to export node introspection: %s\n", err)
|
||||
globalApplication.error("systray error: failed to export node introspection: %w", err)
|
||||
return
|
||||
}
|
||||
menuNode := introspect.Node{
|
||||
@ -329,7 +330,7 @@ func (s *linuxSystemTray) run() {
|
||||
err = conn.Export(introspect.NewIntrospectable(&menuNode), menuPath,
|
||||
"org.freedesktop.DBus.Introspectable")
|
||||
if err != nil {
|
||||
globalApplication.error("systray error: failed to export menu node introspection: %s\n", err)
|
||||
globalApplication.error("systray error: failed to export menu node introspection: %w", err)
|
||||
return
|
||||
}
|
||||
s.setLabel(s.label)
|
||||
@ -344,7 +345,7 @@ func (s *linuxSystemTray) run() {
|
||||
dbus.WithMatchMember("NameOwnerChanged"),
|
||||
dbus.WithMatchArg(0, "org.kde.StatusNotifierWatcher"),
|
||||
); err != nil {
|
||||
globalApplication.error("systray error: failed to register signal matching: %v\n", err)
|
||||
globalApplication.error("systray error: failed to register signal matching: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -388,7 +389,7 @@ func (s *linuxSystemTray) setIcon(icon []byte) {
|
||||
|
||||
iconPx, err := iconToPX(icon)
|
||||
if err != nil {
|
||||
globalApplication.error("systray error: failed to convert icon to PX: %s\n", err)
|
||||
globalApplication.error("systray error: failed to convert icon to PX: %w", err)
|
||||
return
|
||||
}
|
||||
s.props.SetMust("org.kde.StatusNotifierItem", "IconPixmap", []PX{iconPx})
|
||||
@ -402,7 +403,7 @@ func (s *linuxSystemTray) setIcon(icon []byte) {
|
||||
Body: ¬ifier.StatusNotifierItem_NewIconSignalBody{},
|
||||
})
|
||||
if err != nil {
|
||||
globalApplication.error("systray error: failed to emit new icon signal: %s\n", err)
|
||||
globalApplication.error("systray error: failed to emit new icon signal: %w", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -445,7 +446,7 @@ func (s *linuxSystemTray) setLabel(label string) {
|
||||
s.label = label
|
||||
|
||||
if err := s.props.Set("org.kde.StatusNotifierItem", "Title", dbus.MakeVariant(label)); err != nil {
|
||||
globalApplication.error("systray error: failed to set Title prop: %s\n", err)
|
||||
globalApplication.error("systray error: failed to set Title prop: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -457,7 +458,7 @@ func (s *linuxSystemTray) setLabel(label string) {
|
||||
Path: itemPath,
|
||||
Body: ¬ifier.StatusNotifierItem_NewTitleSignalBody{},
|
||||
}); err != nil {
|
||||
globalApplication.error("systray error: failed to emit new title signal: %s", err)
|
||||
globalApplication.error("systray error: failed to emit new title signal: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -591,7 +592,7 @@ func (s *linuxSystemTray) register() bool {
|
||||
obj := s.conn.Object("org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher")
|
||||
call := obj.Call("org.kde.StatusNotifierWatcher.RegisterStatusNotifierItem", 0, itemPath)
|
||||
if call.Err != nil {
|
||||
globalApplication.error("systray error: failed to register: %v\n", call.Err)
|
||||
globalApplication.error("systray error: failed to register: %w", call.Err)
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -3,12 +3,13 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v3/pkg/icons"
|
||||
"errors"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/icons"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
@ -120,7 +121,7 @@ func (s *windowsSystemTray) bounds() (*Rect, error) {
|
||||
|
||||
monitor := w32.MonitorFromWindow(s.hwnd, w32.MONITOR_DEFAULTTONEAREST)
|
||||
if monitor == 0 {
|
||||
return nil, fmt.Errorf("failed to get monitor")
|
||||
return nil, errors.New("failed to get monitor")
|
||||
}
|
||||
|
||||
return &Rect{
|
||||
@ -186,7 +187,7 @@ func (s *windowsSystemTray) run() {
|
||||
for retries := range 6 {
|
||||
if !w32.ShellNotifyIcon(w32.NIM_ADD, &nid) {
|
||||
if retries == 5 {
|
||||
globalApplication.fatal("Failed to register system tray icon: %v", syscall.GetLastError())
|
||||
globalApplication.fatal("failed to register system tray icon: %w", syscall.GetLastError())
|
||||
}
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
@ -1,13 +1,13 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
|
||||
"github.com/leaanthony/u"
|
||||
|
||||
@ -276,7 +276,7 @@ func processKeyBindingOptions(keyBindings map[string]func(window *WebviewWindow)
|
||||
// Parse the key to an accelerator
|
||||
acc, err := parseAccelerator(key)
|
||||
if err != nil {
|
||||
globalApplication.error("Invalid keybinding: %s", err.Error())
|
||||
globalApplication.error("invalid keybinding: %w", err)
|
||||
continue
|
||||
}
|
||||
result[acc.String()] = callback
|
||||
@ -291,40 +291,27 @@ func (w *WebviewWindow) addCancellationFunction(canceller func()) {
|
||||
w.cancellers = append(w.cancellers, canceller)
|
||||
}
|
||||
|
||||
// formatJS ensures the 'data' provided marshals to valid json or panics
|
||||
func (w *WebviewWindow) formatJS(f string, callID string, data string) string {
|
||||
j, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return fmt.Sprintf(f, callID, j)
|
||||
}
|
||||
|
||||
func (w *WebviewWindow) CallError(callID string, result string) {
|
||||
func (w *WebviewWindow) CallError(callID string, result string, isJSON bool) {
|
||||
if w.impl != nil {
|
||||
w.impl.execJS(w.formatJS("_wails.callErrorHandler('%s', %s);", callID, result))
|
||||
w.impl.execJS(fmt.Sprintf("_wails.callErrorHandler('%s', '%s', %t);", callID, template.JSEscapeString(result), isJSON))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WebviewWindow) CallResponse(callID string, result string) {
|
||||
if w.impl != nil {
|
||||
w.impl.execJS(w.formatJS("_wails.callResultHandler('%s', %s, true);", callID, result))
|
||||
w.impl.execJS(fmt.Sprintf("_wails.callResultHandler('%s', '%s', true);", callID, template.JSEscapeString(result)))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WebviewWindow) DialogError(dialogID string, result string) {
|
||||
if w.impl != nil {
|
||||
w.impl.execJS(w.formatJS("_wails.dialogErrorCallback('%s', %s);", dialogID, result))
|
||||
w.impl.execJS(fmt.Sprintf("_wails.dialogErrorCallback('%s', '%s');", dialogID, template.JSEscapeString(result)))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WebviewWindow) DialogResponse(dialogID string, result string, isJSON bool) {
|
||||
if w.impl != nil {
|
||||
if isJSON {
|
||||
w.impl.execJS(w.formatJS("_wails.dialogResultCallback('%s', %s, true);", dialogID, result))
|
||||
} else {
|
||||
w.impl.execJS(fmt.Sprintf("_wails.dialogResultCallback('%s', '%s', false);", dialogID, result))
|
||||
}
|
||||
w.impl.execJS(fmt.Sprintf("_wails.dialogResultCallback('%s', '%s', %t);", dialogID, template.JSEscapeString(result), isJSON))
|
||||
}
|
||||
}
|
||||
|
||||
@ -690,7 +677,7 @@ func (w *WebviewWindow) HandleMessage(message string) {
|
||||
InvokeSync(func() {
|
||||
err := w.startDrag()
|
||||
if err != nil {
|
||||
w.Error("Failed to start drag: %s", err)
|
||||
w.Error("failed to start drag: %w", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -698,12 +685,12 @@ func (w *WebviewWindow) HandleMessage(message string) {
|
||||
if !w.IsFullscreen() {
|
||||
sl := strings.Split(message, ":")
|
||||
if len(sl) != 3 {
|
||||
w.Error("Unknown message returned from dispatcher", "message", message)
|
||||
w.Error("unknown message returned from dispatcher: %s", message)
|
||||
return
|
||||
}
|
||||
err := w.startResize(sl[2])
|
||||
if err != nil {
|
||||
w.Error(err.Error())
|
||||
w.Error("%w", err)
|
||||
}
|
||||
}
|
||||
case message == "wails:runtime:ready":
|
||||
@ -714,7 +701,7 @@ func (w *WebviewWindow) HandleMessage(message string) {
|
||||
w.ExecJS(js)
|
||||
}
|
||||
default:
|
||||
w.Error("Unknown message sent via 'invoke' on frontend: %v", message)
|
||||
w.Error("unknown message sent via 'invoke' on frontend: %v", message)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1162,10 +1149,8 @@ func (w *WebviewWindow) Info(message string, args ...any) {
|
||||
}
|
||||
|
||||
func (w *WebviewWindow) Error(message string, args ...any) {
|
||||
var messageArgs []interface{}
|
||||
messageArgs = append(messageArgs, args...)
|
||||
messageArgs = append(messageArgs, "sender", w.Name())
|
||||
globalApplication.error(message, messageArgs...)
|
||||
args = append([]any{w.Name()}, args...)
|
||||
globalApplication.error("in window '%s': "+message, args...)
|
||||
}
|
||||
|
||||
func (w *WebviewWindow) HandleDragAndDropMessage(filenames []string) {
|
||||
@ -1182,7 +1167,7 @@ func (w *WebviewWindow) OpenContextMenu(data *ContextMenuData) {
|
||||
// try application level context menu
|
||||
menu, ok := globalApplication.getContextMenu(data.Id)
|
||||
if !ok {
|
||||
w.Error("No context menu found for id: %s", data.Id)
|
||||
w.Error("no context menu found for id: %s", data.Id)
|
||||
return
|
||||
}
|
||||
menu.setContextData(data)
|
||||
|
@ -832,7 +832,7 @@ func (w *macosWebviewWindow) handleKeyEvent(acceleratorString string) {
|
||||
// Parse acceleratorString
|
||||
accelerator, err := parseAccelerator(acceleratorString)
|
||||
if err != nil {
|
||||
globalApplication.error("unable to parse accelerator: %s", err.Error())
|
||||
globalApplication.error("unable to parse accelerator: %w", err)
|
||||
return
|
||||
}
|
||||
w.parent.processKeyBinding(accelerator.String())
|
||||
@ -1038,7 +1038,11 @@ func (w *macosWebviewWindow) setEnabled(enabled bool) {
|
||||
|
||||
func (w *macosWebviewWindow) execJS(js string) {
|
||||
InvokeAsync(func() {
|
||||
if globalApplication.performingShutdown {
|
||||
globalApplication.shutdownLock.Lock()
|
||||
performingShutdown := globalApplication.performingShutdown
|
||||
globalApplication.shutdownLock.Unlock()
|
||||
|
||||
if performingShutdown {
|
||||
return
|
||||
}
|
||||
if w.nsWindow == nil {
|
||||
@ -1264,7 +1268,7 @@ func (w *macosWebviewWindow) run() {
|
||||
|
||||
startURL, err := assetserver.GetStartURL(options.URL)
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
|
||||
w.setURL(startURL)
|
||||
|
@ -324,7 +324,7 @@ func (w *linuxWebviewWindow) run() {
|
||||
|
||||
startURL, err := assetserver.GetStartURL(w.parent.options.URL)
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
|
||||
w.setURL(startURL)
|
||||
@ -380,7 +380,7 @@ func (w *linuxWebviewWindow) handleKeyEvent(acceleratorString string) {
|
||||
// Parse acceleratorString
|
||||
// accelerator, err := parseAccelerator(acceleratorString)
|
||||
// if err != nil {
|
||||
// globalApplication.error("unable to parse accelerator: %s", err.Error())
|
||||
// globalApplication.error("unable to parse accelerator: %w", err)
|
||||
// return
|
||||
// }
|
||||
w.parent.processKeyBinding(acceleratorString)
|
||||
|
@ -183,7 +183,7 @@ func (w *windowsWebviewWindow) print() error {
|
||||
|
||||
func (w *windowsWebviewWindow) startResize(border string) error {
|
||||
if !w32.ReleaseCapture() {
|
||||
return fmt.Errorf("unable to release mouse capture")
|
||||
return errors.New("unable to release mouse capture")
|
||||
}
|
||||
// Use PostMessage because we don't want to block the caller until resizing has been finished.
|
||||
w32.PostMessage(w.hwnd, w32.WM_NCLBUTTONDOWN, edgeMap[border], 0)
|
||||
@ -192,7 +192,7 @@ func (w *windowsWebviewWindow) startResize(border string) error {
|
||||
|
||||
func (w *windowsWebviewWindow) startDrag() error {
|
||||
if !w32.ReleaseCapture() {
|
||||
return fmt.Errorf("unable to release mouse capture")
|
||||
return errors.New("unable to release mouse capture")
|
||||
}
|
||||
// Use PostMessage because we don't want to block the caller until dragging has been finished.
|
||||
w32.PostMessage(w.hwnd, w32.WM_NCLBUTTONDOWN, w32.HTCAPTION, 0)
|
||||
@ -334,7 +334,7 @@ func (w *windowsWebviewWindow) run() {
|
||||
nil)
|
||||
|
||||
if w.hwnd == 0 {
|
||||
globalApplication.fatal("Unable to create window")
|
||||
globalApplication.fatal("unable to create window")
|
||||
}
|
||||
|
||||
// Ensure correct window size in case the scale factor of current screen is different from the initial one.
|
||||
@ -1465,7 +1465,7 @@ func (w *windowsWebviewWindow) setWindowMask(imageData []byte) {
|
||||
|
||||
data, err := pngToImage(imageData)
|
||||
if err != nil {
|
||||
globalApplication.fatal("Fatal error in callback setWindowMask: " + err.Error())
|
||||
globalApplication.fatal("fatal error in callback setWindowMask: %w", err)
|
||||
}
|
||||
|
||||
bitmap, err := w32.CreateHBITMAPFromImage(data)
|
||||
@ -1513,15 +1513,15 @@ func (w *windowsWebviewWindow) processRequest(req *edge.ICoreWebView2WebResource
|
||||
useragent = strings.Join([]string{useragent, assetserver.WailsUserAgentValue}, " ")
|
||||
err = reqHeaders.SetHeader(assetserver.HeaderUserAgent, useragent)
|
||||
if err != nil {
|
||||
globalApplication.fatal("Error setting UserAgent header: " + err.Error())
|
||||
globalApplication.fatal("error setting UserAgent header: %w", err)
|
||||
}
|
||||
err = reqHeaders.SetHeader(webViewRequestHeaderWindowId, strconv.FormatUint(uint64(w.parent.id), 10))
|
||||
if err != nil {
|
||||
globalApplication.fatal("Error setting WindowId header: " + err.Error())
|
||||
globalApplication.fatal("error setting WindowId header: %w", err)
|
||||
}
|
||||
err = reqHeaders.Release()
|
||||
if err != nil {
|
||||
globalApplication.fatal("Error releasing headers: " + err.Error())
|
||||
globalApplication.fatal("error releasing headers: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1534,7 +1534,7 @@ func (w *windowsWebviewWindow) processRequest(req *edge.ICoreWebView2WebResource
|
||||
uri, _ := req.GetUri()
|
||||
reqUri, err := url.ParseRequestURI(uri)
|
||||
if err != nil {
|
||||
globalApplication.error("Unable to parse request uri: uri='%s' error='%s'", uri, err)
|
||||
globalApplication.error("unable to parse request uri: uri='%s' error='%w'", uri, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1553,7 +1553,7 @@ func (w *windowsWebviewWindow) processRequest(req *edge.ICoreWebView2WebResource
|
||||
InvokeSync(fn)
|
||||
})
|
||||
if err != nil {
|
||||
globalApplication.error("%s: NewRequest failed: %s", uri, err)
|
||||
globalApplication.error("%s: NewRequest failed: %w", uri, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1572,7 +1572,7 @@ func (w *windowsWebviewWindow) setupChromium() {
|
||||
|
||||
webview2version, err := webviewloader.GetAvailableCoreWebView2BrowserVersionString(globalApplication.options.Windows.WebviewBrowserPath)
|
||||
if err != nil {
|
||||
globalApplication.error("Error getting WebView2 version: " + err.Error())
|
||||
globalApplication.error("error getting WebView2 version: %w", err)
|
||||
return
|
||||
}
|
||||
globalApplication.capabilities = capabilities.NewCapabilities(webview2version)
|
||||
@ -1614,14 +1614,14 @@ func (w *windowsWebviewWindow) setupChromium() {
|
||||
if chromium.HasCapability(edge.SwipeNavigation) {
|
||||
err := chromium.PutIsSwipeNavigationEnabled(opts.EnableSwipeGestures)
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
if chromium.HasCapability(edge.AllowExternalDrop) {
|
||||
err := chromium.AllowExternalDrag(false)
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
}
|
||||
if w.parent.options.EnableDragAndDrop {
|
||||
@ -1657,7 +1657,7 @@ func (w *windowsWebviewWindow) setupChromium() {
|
||||
//if windowName == "Chrome_RenderWidgetHostHWND" {
|
||||
err := w32.RegisterDragDrop(hwnd, w.dropTarget)
|
||||
if err != nil && !errors.Is(err, syscall.Errno(w32.DRAGDROP_E_ALREADYREGISTERED)) {
|
||||
globalApplication.error("Error registering drag and drop: " + err.Error())
|
||||
globalApplication.error("error registering drag and drop: %w", err)
|
||||
}
|
||||
//}
|
||||
return 1
|
||||
@ -1672,7 +1672,7 @@ func (w *windowsWebviewWindow) setupChromium() {
|
||||
// warning
|
||||
globalApplication.warning("unsupported capability: GeneralAutofillEnabled")
|
||||
} else {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1683,7 +1683,7 @@ func (w *windowsWebviewWindow) setupChromium() {
|
||||
if errors.Is(edge.UnsupportedCapabilityError, err) {
|
||||
globalApplication.warning("unsupported capability: PasswordAutosaveEnabled")
|
||||
} else {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1692,7 +1692,7 @@ func (w *windowsWebviewWindow) setupChromium() {
|
||||
//if chromium.HasCapability(edge.AllowExternalDrop) {
|
||||
// err := chromium.AllowExternalDrag(w.parent.options.EnableDragAndDrop)
|
||||
// if err != nil {
|
||||
// globalApplication.fatal(err.Error())
|
||||
// globalApplication.handleFatalError(err)
|
||||
// }
|
||||
// if w.parent.options.EnableDragAndDrop {
|
||||
// chromium.MessageWithAdditionalObjectsCallback = w.processMessageWithAdditionalObjects
|
||||
@ -1702,14 +1702,14 @@ func (w *windowsWebviewWindow) setupChromium() {
|
||||
chromium.Resize()
|
||||
settings, err := chromium.GetSettings()
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
if settings == nil {
|
||||
globalApplication.fatal("Error getting settings")
|
||||
globalApplication.fatal("error getting settings")
|
||||
}
|
||||
err = settings.PutAreDefaultContextMenusEnabled(debugMode || !w.parent.options.DefaultContextMenuDisabled)
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
|
||||
w.enableDevTools(settings)
|
||||
@ -1719,20 +1719,20 @@ func (w *windowsWebviewWindow) setupChromium() {
|
||||
}
|
||||
err = settings.PutIsZoomControlEnabled(w.parent.options.ZoomControlEnabled)
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
|
||||
err = settings.PutIsStatusBarEnabled(false)
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
err = settings.PutAreBrowserAcceleratorKeysEnabled(false)
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
err = settings.PutIsSwipeNavigationEnabled(false)
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
|
||||
if debugMode && w.parent.options.OpenInspectorOnStartup {
|
||||
@ -1761,7 +1761,7 @@ func (w *windowsWebviewWindow) setupChromium() {
|
||||
} else {
|
||||
startURL, err := assetserver.GetStartURL(w.parent.options.URL)
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
w.webviewNavigationCompleted = false
|
||||
chromium.Navigate(startURL)
|
||||
@ -1772,7 +1772,7 @@ func (w *windowsWebviewWindow) setupChromium() {
|
||||
func (w *windowsWebviewWindow) fullscreenChanged(sender *edge.ICoreWebView2, _ *edge.ICoreWebView2ContainsFullScreenElementChangedEventArgs) {
|
||||
isFullscreen, err := sender.GetContainsFullScreenElement()
|
||||
if err != nil {
|
||||
globalApplication.fatal("Fatal error in callback fullscreenChanged: " + err.Error())
|
||||
globalApplication.fatal("fatal error in callback fullscreenChanged: %w", err)
|
||||
}
|
||||
if isFullscreen {
|
||||
w.fullscreen()
|
||||
@ -1817,11 +1817,11 @@ func (w *windowsWebviewWindow) navigationCompleted(sender *edge.ICoreWebView2, a
|
||||
// Hack to make it visible: https://github.com/MicrosoftEdge/WebView2Feedback/issues/1077#issuecomment-825375026
|
||||
err := w.chromium.Hide()
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
err = w.chromium.Show()
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
if wasFocused {
|
||||
w.focus()
|
||||
@ -1839,7 +1839,7 @@ func (w *windowsWebviewWindow) processKeyBinding(vkey uint) bool {
|
||||
// Get the keyboard state and convert to an accelerator
|
||||
var keyState [256]byte
|
||||
if !w32.GetKeyboardState(keyState[:]) {
|
||||
globalApplication.error("Error getting keyboard state")
|
||||
globalApplication.error("error getting keyboard state")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -1890,20 +1890,20 @@ func (w *windowsWebviewWindow) processMessageWithAdditionalObjects(message strin
|
||||
if strings.HasPrefix(message, "FilesDropped") {
|
||||
objs, err := args.GetAdditionalObjects()
|
||||
if err != nil {
|
||||
globalApplication.error(err.Error())
|
||||
globalApplication.handleError(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = objs.Release()
|
||||
if err != nil {
|
||||
globalApplication.error("Error releasing objects: " + err.Error())
|
||||
globalApplication.error("error releasing objects: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
count, err := objs.GetCount()
|
||||
if err != nil {
|
||||
globalApplication.error("cannot get count: %s", err.Error())
|
||||
globalApplication.error("cannot get count: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1911,7 +1911,7 @@ func (w *windowsWebviewWindow) processMessageWithAdditionalObjects(message strin
|
||||
for i := uint32(0); i < count; i++ {
|
||||
_file, err := objs.GetValueAtIndex(i)
|
||||
if err != nil {
|
||||
globalApplication.error("cannot get value at %d : %s", i, err.Error())
|
||||
globalApplication.error("cannot get value at %d: %w", i, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1922,7 +1922,7 @@ func (w *windowsWebviewWindow) processMessageWithAdditionalObjects(message strin
|
||||
|
||||
filepath, err := file.GetPath()
|
||||
if err != nil {
|
||||
globalApplication.error("cannot get path for object at %d : %s", i, err.Error())
|
||||
globalApplication.error("cannot get path for object at %d: %w", i, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1983,7 +1983,7 @@ func NewIconFromResource(instance w32.HINSTANCE, resId uint16) (w32.HICON, error
|
||||
var err error
|
||||
var result w32.HICON
|
||||
if result = w32.LoadIconWithResourceID(instance, resId); result == 0 {
|
||||
err = errors.New(fmt.Sprintf("Cannot load icon from resource with id %v", resId))
|
||||
err = fmt.Errorf("cannot load icon from resource with id %v", resId)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
@ -11,6 +11,6 @@ func (w *windowsWebviewWindow) openDevTools() {
|
||||
func (w *windowsWebviewWindow) enableDevTools(settings *edge.ICoreWebViewSettings) {
|
||||
err := settings.PutAreDevToolsEnabled(true)
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,6 @@ func (w *windowsWebviewWindow) openDevTools() {}
|
||||
func (w *windowsWebviewWindow) enableDevTools(settings *edge.ICoreWebViewSettings) {
|
||||
err := settings.PutAreDevToolsEnabled(false)
|
||||
if err != nil {
|
||||
globalApplication.fatal(err.Error())
|
||||
globalApplication.handleFatalError(err)
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
)
|
||||
|
||||
type Callback interface {
|
||||
CallError(callID string, result string)
|
||||
CallError(callID string, result string, isJSON bool)
|
||||
CallResponse(callID string, result string)
|
||||
DialogError(dialogID string, result string)
|
||||
DialogResponse(dialogID string, result string, isJSON bool)
|
||||
|
Loading…
Reference in New Issue
Block a user